source: main/waeup.kofa/trunk/src/waeup/kofa/applicants/tests/test_browser.py @ 15539

Last change on this file since 15539 was 15287, checked in by Henrik Bettermann, 6 years ago

Stored insecure passwords are no longer accepted.
Officers with an insecure password can't login and are
redirected to the ChangePasswordRequestPage to request a
new password.

  • Property svn:keywords set to Id
File size: 86.8 KB
Line 
1## $Id: test_browser.py 15287 2019-01-09 21:17:08Z henrik $
2##
3## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
4## This program is free software; you can redistribute it and/or modify
5## it under the terms of the GNU General Public License as published by
6## the Free Software Foundation; either version 2 of the License, or
7## (at your option) any later version.
8##
9## This program is distributed in the hope that it will be useful,
10## but WITHOUT ANY WARRANTY; without even the implied warranty of
11## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12## GNU General Public License for more details.
13##
14## You should have received a copy of the GNU General Public License
15## along with this program; if not, write to the Free Software
16## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17##
18"""
19Test the applicant-related UI components.
20"""
21import os
22import pytz
23import shutil
24import tempfile
25import grok
26import logging
27from datetime import datetime
28from StringIO import StringIO
29from datetime import datetime, date, timedelta
30from mechanize import LinkNotFoundError
31from zc.async.testing import wait_for_result
32from zope.securitypolicy.interfaces import IPrincipalRoleManager
33from zope.event import notify
34from zope.catalog.interfaces import ICatalog
35from zope.component import createObject, getUtility
36from zope.component.hooks import setSite, clearSite
37from zope.security.interfaces import Unauthorized
38from zope.testbrowser.testing import Browser
39from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
40from waeup.kofa.testing import FunctionalLayer, FunctionalTestCase
41from waeup.kofa.app import University
42from waeup.kofa.payments.interfaces import IPayer
43from waeup.kofa.configuration import SessionConfiguration
44from waeup.kofa.refereeentries import RefereeEntry
45from waeup.kofa.applicants.container import ApplicantsContainer
46from waeup.kofa.applicants.applicant import Applicant
47from waeup.kofa.interfaces import (
48    IExtFileStore, IFileStoreNameChooser, IUserAccount, IJobManager)
49from waeup.kofa.university.faculty import Faculty
50from waeup.kofa.university.department import Department
51from waeup.kofa.mandates.mandate import RefereeReportMandate
52from waeup.kofa.tests.test_async import FunctionalAsyncTestCase
53from waeup.kofa.tests.test_authentication import SECRET
54
55PH_LEN = 15911  # Length of placeholder file
56
57session_1 = datetime.now().year - 2
58container_name_1 = u'app%s' % session_1
59session_2 = datetime.now().year - 1
60container_name_2 = u'app%s' % session_2
61
62SAMPLE_IMAGE = os.path.join(os.path.dirname(__file__), 'test_image.jpg')
63
64class ApplicantsFullSetup(FunctionalTestCase):
65    # A test case that only contains a setup and teardown
66    #
67    # Complete setup for applicants handlings is rather complex and
68    # requires lots of things created before we can start. This is a
69    # setup that does all this, creates a university, creates PINs,
70    # etc.  so that we do not have to bother with that in different
71    # test cases.
72
73    layer = FunctionalLayer
74
75    def setUp(self):
76        super(ApplicantsFullSetup, self).setUp()
77
78        # Setup a sample site for each test
79        app = University()
80        self.dc_root = tempfile.mkdtemp()
81        app['datacenter'].setStoragePath(self.dc_root)
82
83        # Prepopulate the ZODB...
84        self.getRootFolder()['app'] = app
85        # we add the site immediately after creation to the
86        # ZODB. Catalogs and other local utilities are not setup
87        # before that step.
88        self.app = self.getRootFolder()['app']
89        # Set site here. Some of the following setup code might need
90        # to access grok.getSite() and should get our new app then
91        setSite(app)
92
93        self.login_path = 'http://localhost/app/login'
94        self.root_path = 'http://localhost/app/applicants'
95        self.search_path = 'http://localhost/app/applicants/search'
96        self.manage_root_path = self.root_path + '/@@manage'
97        self.add_container_path = self.root_path + '/@@add'
98        self.container_path = 'http://localhost/app/applicants/%s' % container_name_1
99        self.manage_container_path = self.container_path + '/@@manage'
100
101        # Add an applicants container
102        applicantscontainer = ApplicantsContainer()
103        applicantscontainer.code = container_name_1
104        applicantscontainer.prefix = 'app'
105        applicantscontainer.year = session_1
106        applicantscontainer.title = u'This is the %s container' % container_name_1
107        applicantscontainer.application_category = 'basic'
108        applicantscontainer.mode = 'create'
109        applicantscontainer.strict_deadline = True
110        delta = timedelta(days=10)
111        applicantscontainer.startdate = datetime.now(pytz.utc) - delta
112        applicantscontainer.enddate = datetime.now(pytz.utc) + delta
113        self.app['applicants'][container_name_1] = applicantscontainer
114        self.applicantscontainer = self.app['applicants'][container_name_1]
115
116        # Populate university
117        certificate = createObject('waeup.Certificate')
118        certificate.code = 'CERT1'
119        certificate.application_category = 'basic'
120        certificate.start_level = 100
121        certificate.end_level = 500
122        certificate.study_mode = u'ug_ft'
123        self.certificate = certificate
124        self.app['faculties']['fac1'] = Faculty()
125        # The code has explicitely to be set, otherwise we don't
126        # find created students in their department
127        self.app['faculties']['fac1']['dep1'] = Department(code=u'dep1')
128        self.department = self.app['faculties']['fac1']['dep1']
129        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
130            certificate)
131
132        # Put the prepopulated site into test ZODB and prepare test
133        # browser
134        self.browser = Browser()
135        self.browser.handleErrors = False
136
137        # Create 5 access codes with prefix'FOO' and cost 9.99 each
138        pin_container = self.app['accesscodes']
139        pin_container.createBatch(
140            datetime.now(), 'some_userid', 'APP', 9.99, 5)
141        pins = pin_container[pin_container.keys()[0]].values()
142        self.pins = [x.representation for x in pins]
143        self.existing_pin = self.pins[0]
144        parts = self.existing_pin.split('-')[1:]
145        self.existing_series, self.existing_number = parts
146
147        # Add an applicant
148        self.applicant = createObject('waeup.Applicant')
149        # reg_number is the only field which has to be preset here
150        # because managers are allowed to edit this required field
151        self.applicant.firstname = u'Joan'
152        self.applicant.reg_number = u'1234'
153        self.applicant.course1 = certificate
154        app['applicants'][container_name_1].addApplicant(self.applicant)
155        IUserAccount(
156            self.app['applicants'][container_name_1][
157            self.applicant.application_number]).setPassword('apwd')
158        self.manage_path = 'http://localhost/app/applicants/%s/%s/%s' % (
159            container_name_1, self.applicant.application_number, 'manage')
160        self.edit_path = 'http://localhost/app/applicants/%s/%s/%s' % (
161            container_name_1, self.applicant.application_number, 'edit')
162        self.view_path = 'http://localhost/app/applicants/%s/%s' % (
163            container_name_1, self.applicant.application_number)
164
165    def login(self):
166        # Perform an applicant login. This creates an application record.
167        #
168        # This helper also sets `self.applicant`, which is the
169        # applicant object created.
170        self.browser.open(self.login_path)
171        self.browser.getControl(
172            name="form.login").value = self.applicant.applicant_id
173        self.browser.getControl(name="form.password").value = 'apwd'
174        self.browser.getControl("Login").click()
175
176    def fill_correct_values(self):
177        # Fill the edit form with suitable values
178        self.browser.getControl(name="form.firstname").value = 'John'
179        self.browser.getControl(name="form.middlename").value = 'Anthony'
180        self.browser.getControl(name="form.lastname").value = 'Tester'
181        self.browser.getControl(name="form.course1").value = ['CERT1']
182        self.browser.getControl(name="form.date_of_birth").value = '09/09/1988'
183        self.browser.getControl(name="form.sex").value = ['m']
184        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
185
186    def tearDown(self):
187        super(ApplicantsFullSetup, self).tearDown()
188        clearSite()
189        shutil.rmtree(self.dc_root)
190
191class ApplicantsRootUITests(ApplicantsFullSetup):
192    # Tests for ApplicantsRoot class
193
194    layer = FunctionalLayer
195
196    def test_anonymous_access(self):
197        # Anonymous users can access applicants root
198        self.browser.open(self.root_path)
199        self.assertEqual(self.browser.headers['Status'], '200 Ok')
200        self.assertFalse(
201            'Manage ' in self.browser.contents)
202        return
203
204    def test_anonymous_no_actions(self):
205        # Make sure anonymous users cannot access actions
206        self.browser.open(self.root_path)
207        self.assertRaises(
208            LookupError, self.browser.getControl, "Add local role")
209        # Manage screen neither linked nor accessible for anonymous
210        self.assertRaises(
211            LinkNotFoundError,
212            self.browser.getLink, 'Manage applicants section')
213        self.assertRaises(
214            Unauthorized, self.browser.open, self.manage_root_path)
215        # Add container screen not accessible for anonymous
216        self.assertRaises(
217            Unauthorized, self.browser.open, self.add_container_path)
218        return
219
220    def test_manage_access(self):
221        # Managers can access the manage pages of applicants root
222        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
223        self.browser.open(self.root_path)
224        self.assertTrue('Manage applicants section' in self.browser.contents)
225        # There is a manage link
226        link = self.browser.getLink('Manage applicants section')
227        link.click()
228        self.assertEqual(self.browser.headers['Status'], '200 Ok')
229        self.assertEqual(self.browser.url, self.manage_root_path)
230        return
231
232    def test_hide_container(self):
233        self.browser.open(self.root_path)
234        self.assertTrue(
235            '<a href="http://localhost/app/applicants/%s">'
236            'This is the %s container</a>' % (container_name_1, container_name_1)
237            in self.browser.contents)
238        self.app['applicants'][container_name_1].hidden = True
239        self.browser.open(self.root_path)
240        # Anonymous users can't see hidden containers
241        self.assertFalse(
242            '<a href="http://localhost/app/applicants/%s">'
243            'This is the %s container</a>' % (container_name_1, container_name_1)
244            in self.browser.contents)
245        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
246        self.browser.open(self.root_path)
247        self.assertTrue(
248            '<a href="http://localhost/app/applicants/%s">'
249            'This is the %s container</a>' % (container_name_1, container_name_1)
250            in self.browser.contents)
251        return
252
253    def test_search(self):
254        # Managers can access the manage pages of applicants root
255        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
256        self.browser.open(self.manage_path)
257        self.fill_correct_values()
258        self.browser.getControl("Save").click()
259        self.browser.open(self.root_path)
260        self.assertTrue('Manage applicants section' in self.browser.contents)
261        # There is a search link
262        link = self.browser.getLink('Find applicants')
263        link.click()
264        self.assertEqual(self.browser.headers['Status'], '200 Ok')
265        # We can find an applicant ...
266        # ... via his name
267        self.browser.getControl(name="searchtype").value = ['fullname']
268        self.browser.getControl(name="searchterm").value = 'John'
269        self.browser.getControl("Find applicant").click()
270        self.assertTrue('John Anthony Tester' in self.browser.contents)
271        self.browser.getControl(name="searchtype").value = ['fullname']
272        self.browser.getControl(name="searchterm").value = 'Tester'
273        self.browser.getControl("Find applicant").click()
274        self.assertTrue('John Anthony Tester' in self.browser.contents)
275        self.browser.open(self.search_path)
276        # ... and via his reg_number ...
277        self.browser.getControl(name="searchtype").value = ['reg_number']
278        self.browser.getControl(name="searchterm").value = '2345'
279        self.browser.getControl("Find applicant").click()
280        self.assertFalse('John Anthony Tester' in self.browser.contents)
281        self.browser.getControl(name="searchtype").value = ['reg_number']
282        self.browser.getControl(name="searchterm").value = '1234'
283        self.browser.getControl("Find applicant").click()
284        self.assertTrue('John Anthony Tester' in self.browser.contents)
285        # ... and not via his application_number ...
286        self.browser.getControl(name="searchtype").value = ['applicant_id']
287        self.browser.getControl(
288            name="searchterm").value = self.applicant.application_number
289        self.browser.getControl("Find applicant").click()
290        self.assertFalse('John Anthony Tester' in self.browser.contents)
291        # ... but ia his applicant_id ...
292        self.browser.getControl(name="searchtype").value = ['applicant_id']
293        self.browser.getControl(
294            name="searchterm").value = self.applicant.applicant_id
295        self.browser.getControl("Find applicant").click()
296        self.assertTrue('John Anthony Tester' in self.browser.contents)
297        # ... and via his email
298        self.browser.getControl(name="searchtype").value = ['email']
299        self.browser.getControl(name="searchterm").value = 'xx@yy.zz'
300        self.browser.getControl("Find applicant").click()
301        self.assertTrue('John Anthony Tester' in self.browser.contents)
302        return
303
304    def test_manage_actions_access(self):
305        # Managers can access the action on manage screen
306        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
307        self.browser.open(self.manage_root_path)
308        self.browser.getControl("Add local role").click()
309        self.assertTrue('No user selected' in self.browser.contents)
310        return
311
312    def test_local_roles_add_delete(self):
313        # Managers can assign and delete local roles of applicants root
314        myusers = self.app['users']
315        myusers.addUser('bob', 'bobssecret')
316        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
317        self.browser.open('http://localhost/app/faculties/fac1/dep1/manage')
318        self.browser.getControl(name="user").value = ['bob']
319        self.browser.getControl(name="local_role").value = [
320            'waeup.local.ApplicationsManager']
321        self.browser.getControl("Add local role").click()
322        self.assertTrue('<td>bob</td>' in self.browser.contents)
323        # Remove the role assigned
324        ctrl = self.browser.getControl(name='role_id')
325        ctrl.getControl(
326            value='bob|waeup.local.ApplicationsManager').selected = True
327        self.browser.getControl("Remove selected local roles").click()
328        self.assertTrue(
329            'Local role successfully removed: bob|waeup.local.ApplicationsManager'
330            in self.browser.contents)
331        self.assertFalse('<td>bob</td>' in self.browser.contents)
332        return
333
334    def test_add_delete_container(self):
335        # Managers can add and delete applicants containers
336        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
337        self.browser.open(self.manage_root_path)
338        self.browser.getControl("Add applicants container").click()
339        self.assertEqual(self.browser.headers['Status'], '200 Ok')
340        self.assertEqual(self.browser.url, self.add_container_path)
341        self.browser.getControl(name="form.prefix").value = ['app']
342        self.browser.getControl("Add applicants container").click()
343        self.assertTrue(
344            'There were errors' in self.browser.contents)
345        self.browser.getControl(name="form.prefix").value = ['app']
346        self.browser.getControl(name="form.year").value = [str(session_2)]
347        self.browser.getControl(name="form.mode").value = ['create']
348        self.browser.getControl(
349            name="form.application_category").value = ['basic']
350        self.browser.getControl("Add applicants container").click()
351        self.assertTrue('Added:' in self.browser.contents)
352        self.browser.getLink(container_name_1).click()
353        self.assertTrue('Manage applicants container'
354            in self.browser.contents)
355        self.browser.open(self.add_container_path)
356        self.browser.getControl("Cancel").click()
357        self.assertEqual(self.browser.url, self.manage_root_path)
358        self.browser.open(self.add_container_path)
359        self.browser.getControl(name="form.prefix").value = ['app']
360        self.browser.getControl(name="form.year").value = [str(session_2)]
361        self.browser.getControl(name="form.mode").value = ['create']
362        self.browser.getControl(
363            name="form.application_category").value = ['basic']
364        self.browser.getControl("Add applicants container").click()
365        self.assertTrue('exists already in the database'
366                        in self.browser.contents)
367        self.browser.open(self.manage_root_path)
368        ctrl = self.browser.getControl(name='val_id')
369        ctrl.getControl(value=container_name_2).selected = True
370        self.browser.getControl("Remove selected", index=0).click()
371        self.assertTrue('Successfully removed:' in self.browser.contents)
372        self.browser.open(self.add_container_path)
373        self.browser.getControl(name="form.prefix").value = ['app']
374        self.browser.getControl(name="form.year").value = [str(session_2)]
375        self.browser.getControl(name="form.mode").value = ['create']
376        #self.browser.getControl(name="form.ac_prefix").value = ['APP']
377        self.browser.getControl(
378            name="form.application_category").value = ['basic']
379        self.browser.getControl("Add applicants container").click()
380        del self.app['applicants'][container_name_2]
381        ctrl = self.browser.getControl(name='val_id')
382        ctrl.getControl(value=container_name_2).selected = True
383        self.browser.getControl("Remove selected", index=0).click()
384        self.assertMatches('...Could not delete...', self.browser.contents)
385        return
386
387class ApplicantsContainerUITests(ApplicantsFullSetup):
388    # Tests for ApplicantsContainer class views and pages
389
390    layer = FunctionalLayer
391
392    def test_anonymous_access(self):
393        # Anonymous users can access applicants containers
394        self.browser.open(self.container_path)
395        self.assertEqual(self.browser.headers['Status'], '200 Ok')
396        self.assertFalse(
397            'Manage ' in self.browser.contents)
398        return
399
400    def test_manage_access(self):
401        # Managers can access the manage pages of applicants
402        # containers and can perform actions
403        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
404        self.browser.open(self.manage_container_path)
405        self.assertEqual(self.browser.headers['Status'], '200 Ok')
406        self.assertEqual(self.browser.url, self.manage_container_path)
407        self.browser.getControl(name="form.application_fee").value = '200'
408        self.browser.getControl("Save").click()
409        self.assertTrue('Form has been saved' in self.browser.contents)
410        logfile = os.path.join(
411            self.app['datacenter'].storage, 'logs', 'applicants.log')
412        logcontent = open(logfile).read()
413        self.assertTrue(
414            'zope.mgr - applicants.browser.ApplicantsContainerManageFormPage - '
415            '%s - saved: application_fee\n' % container_name_1 in logcontent)
416        self.browser.getControl("Remove selected", index=0).click()
417        self.assertTrue('No applicant selected' in self.browser.contents)
418        self.browser.getControl("Add local role").click()
419        self.assertTrue('No user selected' in self.browser.contents)
420        self.browser.getControl("Cancel", index=0).click()
421        self.assertEqual(self.browser.url, self.container_path)
422        return
423
424    def test_statistics(self):
425        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
426        self.browser.open(self.container_path)
427        self.browser.getLink("Container statistics").click()
428        self.assertTrue('<td>initialized</td>' in self.browser.contents)
429        self.assertTrue('<td>1</td>' in self.browser.contents)
430        self.assertEqual(self.applicantscontainer.statistics[0],
431            {'not admitted': 0, 'started': 0, 'created': 0,
432            'admitted': 0, 'submitted': 0, 'initialized': 1,
433            'paid': 0, 'processed': 0})
434        #self.assertEqual(self.applicantscontainer.statistics[1],
435        #    {u'fac1': 0})
436        IWorkflowState(self.applicant).setState('submitted')
437        notify(grok.ObjectModifiedEvent(self.applicant))
438        self.assertEqual(self.applicantscontainer.statistics[0],
439            {'not admitted': 0, 'started': 0, 'created': 0,
440            'admitted': 0, 'submitted': 1, 'initialized': 0, 'paid': 0,
441            'processed': 0})
442        #self.assertEqual(self.applicantscontainer.statistics[1],
443        #    {u'fac1': 1})
444        return
445
446    def test_add_delete_applicants(self):
447        # Check the global role map first
448        role_manager = IPrincipalRoleManager(grok.getSite())
449        principals = role_manager.getPrincipalsForRole('waeup.Applicant')
450        self.assertEqual(len(principals), 1)
451        self.assertEqual(principals[0][0], self.applicant.applicant_id)
452        # Managers can add and delete applicants
453        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
454        self.add_applicant_path = self.container_path + '/addapplicant'
455        self.container_manage_path = self.container_path + '/@@manage'
456        self.browser.open(self.container_manage_path)
457        self.browser.getLink("Add applicant").click()
458        self.assertEqual(self.browser.headers['Status'], '200 Ok')
459        self.assertEqual(self.browser.url, self.add_applicant_path)
460        self.browser.getControl(name="form.firstname").value = 'Alois'
461        self.browser.getControl(name="form.middlename").value = 'Kofi'
462        self.browser.getControl(name="form.lastname").value = 'Bettermann'
463        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
464        self.browser.getControl("Create application record").click()
465        self.assertTrue('Application initialized' in self.browser.contents)
466        # The global role map has been extended
467        role_manager = IPrincipalRoleManager(grok.getSite())
468        principals = role_manager.getPrincipalsForRole('waeup.Applicant')
469        self.assertEqual(len(principals), 2)
470        self.browser.open(self.container_manage_path)
471        self.assertEqual(self.browser.headers['Status'], '200 Ok')
472        ctrl = self.browser.getControl(name='val_id')
473        value = ctrl.options[0]
474        ctrl.getControl(value=value).selected = True
475        self.browser.getControl("Remove selected", index=0).click()
476        self.assertTrue('Successfully removed:' in self.browser.contents)
477        # The global role map has been reduced
478        role_manager = IPrincipalRoleManager(grok.getSite())
479        principals = role_manager.getPrincipalsForRole('waeup.Applicant')
480        self.assertEqual(len(principals), 1)
481        self.browser.open(self.add_applicant_path)
482        self.browser.getControl(name="form.firstname").value = 'Albert'
483        self.browser.getControl(name="form.lastname").value = 'Einstein'
484        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
485        self.browser.getControl("Create application record").click()
486        self.assertTrue('Application initialized' in self.browser.contents)
487        return
488
489    def test_prefill_purge_container(self):
490        # Managers can pre-fill containers in create mode
491        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
492        self.prefill_path = self.container_path + '/prefill'
493        self.container_manage_path = self.container_path + '/@@manage'
494        self.browser.open(self.container_manage_path)
495        self.browser.getLink("Pre-fill").click()
496        self.assertEqual(self.browser.headers['Status'], '200 Ok')
497        self.assertEqual(self.browser.url, self.prefill_path)
498        self.browser.getControl(name="number").value = ['10']
499        self.browser.getControl("Pre-fill").click()
500        self.assertTrue('10 application records created.' in self.browser.contents)
501        self.browser.open(self.container_manage_path)
502        self.assertTrue('This container contains 10 unused pre-filled records.'
503            in self.browser.contents)
504        self.assertEqual(self.applicantscontainer.counts[0], 11)
505        self.assertEqual(self.applicantscontainer.counts[1], 1)
506        # In update mode we can't pre-fill the container
507        self.applicantscontainer.mode = 'update'
508        self.browser.open(self.container_manage_path)
509        self.browser.getLink("Pre-fill").click()
510        self.assertTrue('Container must be in create mode to be pre-filled.'
511            in self.browser.contents)
512        self.browser.open(self.manage_root_path)
513        # Number of total records is 11
514        self.assertTrue('<td>11</td>' in self.browser.contents)
515        # The statistics have not changed
516        self.browser.open(self.container_path)
517        self.browser.getLink("Container statistics").click()
518        self.assertTrue('<td>1</td>' in self.browser.contents)
519        self.assertEqual(self.applicantscontainer.statistics[0],
520            {'not admitted': 0, 'started': 0, 'created': 0,
521            'admitted': 0, 'submitted': 0, 'initialized': 1,
522            'paid': 0, 'processed': 0})
523        # Container can be purged
524        IWorkflowState(self.applicant).setState('submitted')
525        self.browser.open(self.container_manage_path)
526        self.browser.getLink("Purge").click()
527        self.browser.getControl("Remove").click()
528        self.assertTrue('10 application records purged' in self.browser.contents)
529        self.assertEqual(self.applicantscontainer.counts[0], 1)
530        self.assertEqual(self.applicantscontainer.counts[1], 1)
531        IWorkflowState(self.applicant).setState('initialized')
532        self.browser.open(self.container_manage_path)
533        self.browser.getLink("Purge").click()
534        self.browser.getControl("Remove").click()
535        self.assertTrue('1 application records purged' in self.browser.contents)
536        self.assertEqual(self.applicantscontainer.counts[0], 0)
537        self.assertEqual(self.applicantscontainer.counts[1], 0)
538        return
539
540    def init_officer(self):
541        # Create application officer
542        self.app['users'].addUser('mrappl', SECRET)
543        self.app['users']['mrappl'].email = 'mrappl@foo.ng'
544        self.app['users']['mrappl'].title = 'Carlo Pitter'
545        prmglobal = IPrincipalRoleManager(self.app)
546        prmglobal.assignRoleToPrincipal('waeup.ApplicationsManager', 'mrappl')
547        # Login as officer
548        self.browser.open(self.login_path)
549        self.browser.getControl(name="form.login").value = 'mrappl'
550        self.browser.getControl(name="form.password").value = SECRET
551        self.browser.getControl("Login").click()
552
553    def test_student_creation_permission(self):
554        self.init_officer()
555        self.browser.open(self.container_path + '/manage')
556        self.browser.getControl("Create students").click()
557        self.assertTrue('You don\'t have permission to create student records'
558            in self.browser.contents)
559        prmglobal = IPrincipalRoleManager(self.app)
560        prmglobal.assignRoleToPrincipal('waeup.StudentsCreator', 'mrappl')
561        self.browser.getControl("Create students").click()
562        self.assertTrue('No applicant selected' in self.browser.contents)
563
564
565class ApplicantUITests(ApplicantsFullSetup):
566    # Tests for uploading/browsing the passport image of appplicants
567
568    layer = FunctionalLayer
569
570    def test_manage_and_view_applicant(self):
571        # Managers can manage applicants
572        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
573        self.slip_path = self.view_path + '/application_slip.pdf'
574        self.browser.open(self.manage_path)
575        self.assertEqual(self.browser.headers['Status'], '200 Ok')
576        self.fill_correct_values()
577        # Fire transition
578        self.browser.getControl(name="transition").value = ['start']
579        self.browser.getControl("Save").click()
580        # Be sure that the empty phone field does not show wrong error message
581        self.assertFalse('Required input is missing' in self.browser.contents)
582        self.assertMatches('...Form has been saved...', self.browser.contents)
583        self.assertMatches('...Application started by Manager...',
584                           self.browser.contents)
585        self.browser.open(self.view_path)
586        self.assertEqual(self.browser.headers['Status'], '200 Ok')
587        # Change course_admitted
588        self.browser.open(self.manage_path)
589        self.browser.getControl(name="form.course_admitted").value = ['CERT1']
590        self.browser.getControl("Save").click()
591        self.assertMatches('...Form has been saved...', self.browser.contents)
592        # Change password
593        self.browser.getControl(name="password").value = 'secret'
594        self.browser.getControl(name="control_password").value = 'secre'
595        self.browser.getControl("Save").click()
596        self.assertMatches('...Passwords do not match...',
597                           self.browser.contents)
598        self.browser.getControl(name="password").value = 'secret'
599        self.browser.getControl(name="control_password").value = 'secret'
600        self.browser.getControl("Save").click()
601        self.assertMatches('...Form has been saved...', self.browser.contents)
602        # Pdf slip can't be opened and download button is not available
603        self.assertFalse('Download application slip' in self.browser.contents)
604        self.browser.open(self.slip_path)
605        self.assertTrue(
606            'Please pay and submit before trying to download the application slip.'
607            in self.browser.contents)
608        # If applicant is in correct state the pdf slip can be opened.
609        IWorkflowState(self.applicant).setState('submitted')
610        self.browser.open(self.manage_path)
611        self.browser.getLink("Download application slip").click()
612        self.assertEqual(self.browser.headers['Status'], '200 Ok')
613        self.assertEqual(self.browser.headers['Content-Type'],
614                         'application/pdf')
615        # Managers can view applicants even if certificate has been removed
616        del self.app['faculties']['fac1']['dep1'].certificates['CERT1']
617        self.browser.open(self.view_path)
618        self.assertEqual(self.browser.headers['Status'], '200 Ok')
619        self.browser.open(self.slip_path)
620        self.assertEqual(self.browser.headers['Status'], '200 Ok')
621        return
622
623    def test_passport_edit_view(self):
624        # We get a default image after login
625        self.browser.open(self.login_path)
626        self.login()
627        self.browser.open(self.browser.url + '/passport.jpg')
628        self.assertEqual(self.browser.headers['status'], '200 Ok')
629        self.assertEqual(self.browser.headers['content-type'], 'image/jpeg')
630        self.assertTrue('JFIF' in self.browser.contents)
631        self.assertEqual(
632            self.browser.headers['content-length'], str(PH_LEN))
633
634    def test_applicant_login(self):
635        self.applicant.suspended = True
636        self.login()
637        self.assertTrue(
638            'You entered invalid credentials.' in self.browser.contents)
639        self.applicant.suspended = False
640        self.browser.getControl("Login").click()
641        self.assertTrue(
642            'You logged in.' in self.browser.contents)
643
644    def test_maintenance_mode(self):
645        config = grok.getSite()['configuration']
646        self.login()
647        # Applicant  logged in.
648        self.assertTrue('You logged in' in self.browser.contents)
649        self.assertTrue("Joan None" in self.browser.contents)
650        # If maintenance mode is enabled, applicant is immediately logged out.
651        config.maintmode_enabled_by = u'any_user'
652        self.assertRaises(
653            Unauthorized, self.browser.open, 'http://localhost/app/faculties')
654        self.browser.open('http://localhost/app/login')
655        self.assertTrue('The portal is in maintenance mode' in self.browser.contents)
656        # Applicant really can't login if maintenance mode is enabled.
657        self.login()
658        # A second warning is raised.
659        self.assertTrue(
660            'The portal is in maintenance mode. You can\'t login!'
661            in self.browser.contents)
662        return
663
664    def test_applicant_access(self):
665        # Applicants can edit their record
666        self.browser.open(self.login_path)
667        self.login()
668        self.assertTrue(
669            'You logged in.' in self.browser.contents)
670        self.browser.open(self.edit_path)
671        self.assertTrue(self.browser.url != self.login_path)
672        self.assertEqual(self.browser.headers['Status'], '200 Ok')
673        self.fill_correct_values()
674        self.assertTrue(IUserAccount(self.applicant).checkPassword('apwd'))
675        self.browser.getControl("Save").click()
676        self.assertMatches('...Form has been saved...', self.browser.contents)
677        # Applicants don't see manage and search links ...
678        self.browser.open(self.root_path)
679        self.assertEqual(self.browser.headers['Status'], '200 Ok')
680        self.assertFalse('Search' in self.browser.contents)
681        self.assertFalse('Manage applicants section' in self.browser.contents)
682        # ... and can't access the manage page
683        self.assertRaises(
684            Unauthorized, self.browser.open, self.manage_path)
685        return
686
687    def test_message_for_created(self):
688        IWorkflowState(self.applicant).setState('created')
689        self.applicant.student_id = u'my id'
690        self.browser.open(self.login_path)
691        self.login()
692        self.assertTrue(
693            'You logged in.' in self.browser.contents)
694        self.assertTrue(
695            '<strong>Congratulations!</strong> You have been offered provisional'
696            ' admission into the %s/%s Academic Session of'
697            ' Sample University. Your student record has been created for you.'
698            % (session_1, session_1 + 1) in self.browser.contents)
699        self.assertTrue(
700            'Then enter your new student credentials: user name= my id,'
701            ' password = %s.' % self.applicant.application_number
702            in self.browser.contents)
703        return
704
705    def image_url(self, filename):
706        return self.edit_path.replace('edit', filename)
707
708    def test_after_login_default_browsable(self):
709        # After login we see the placeholder image in the edit view
710        self.login()
711        self.assertEqual(self.browser.url, self.view_path)
712        self.browser.open(self.edit_path)
713        # There is a correct <img> link included
714        self.assertTrue(
715              '<img src="passport.jpg" height="180px" />' in self.browser.contents)
716        # Browsing the link shows a real image
717        self.browser.open(self.image_url('passport.jpg'))
718        self.assertEqual(
719            self.browser.headers['content-type'], 'image/jpeg')
720        self.assertEqual(len(self.browser.contents), PH_LEN)
721
722    def test_after_submit_default_browsable(self):
723        # After submitting an applicant form the default image is
724        # still visible
725        self.login()
726        self.browser.open(self.edit_path)
727        self.browser.getControl("Save").click() # submit form
728        # There is a correct <img> link included
729        self.assertTrue(
730            '<img src="passport.jpg" height="180px" />' in self.browser.contents)
731        # Browsing the link shows a real image
732        self.browser.open(self.image_url('passport.jpg'))
733        self.assertEqual(
734            self.browser.headers['content-type'], 'image/jpeg')
735        self.assertEqual(len(self.browser.contents), PH_LEN)
736
737    def test_uploaded_image_respects_file_size_restriction(self):
738        # When we upload an image that is too big ( > 10 KB) we will
739        # get an error message
740        self.login()
741        self.browser.open(self.edit_path)
742        # Create a pseudo image file and select it to be uploaded in form
743        photo_content = 'A' * 1024 * 51  # A string of 11 KB size
744        pseudo_image = StringIO(photo_content)
745        ctrl = self.browser.getControl(name='form.passport')
746        file_ctrl = ctrl.mech_control
747        file_ctrl.add_file(pseudo_image, filename='myphoto.jpg')
748        self.browser.getControl("Save").click() # submit form
749        # There is a correct <img> link included
750        self.assertTrue(
751            '<img src="passport.jpg" height="180px" />' in self.browser.contents)
752        # We get a warning message
753        self.assertTrue(
754            'Uploaded image is too big' in self.browser.contents)
755        # Browsing the image gives us the default image, not the
756        # uploaded one.
757        self.browser.open(self.image_url('passport.jpg'))
758        self.assertEqual(
759            self.browser.headers['content-type'], 'image/jpeg')
760        self.assertEqual(len(self.browser.contents), PH_LEN)
761        # There is really no file stored for the applicant
762        img = getUtility(IExtFileStore).getFile(
763            IFileStoreNameChooser(self.applicant).chooseName())
764        self.assertTrue(img is None)
765
766    def test_uploaded_image_browsable_w_errors(self):
767        # We can upload a different image and browse it after submit,
768        # even if there are still errors in the form
769        self.login()
770        self.browser.open(self.edit_path)
771        # Create a pseudo image file and select it to be uploaded in form
772        photo_content = 'I pretend to be a graphics file'
773        pseudo_image = StringIO(photo_content)
774        ctrl = self.browser.getControl(name='form.passport')
775        file_ctrl = ctrl.mech_control
776        file_ctrl.add_file(pseudo_image, filename='myphoto.jpg')
777        self.browser.getControl("Save").click() # submit form
778        # There is a correct <img> link included
779        self.assertTrue(
780            '<img src="passport.jpg" height="180px" />' in self.browser.contents)
781        # Browsing the link shows a real image
782        self.browser.open(self.image_url('passport.jpg'))
783        self.assertEqual(
784            self.browser.headers['content-type'], 'image/jpeg')
785        self.assertEqual(self.browser.contents, photo_content)
786
787    def test_uploaded_image_stored_in_imagestorage_w_errors(self):
788        # After uploading a new passport pic the file is correctly
789        # stored in an imagestorage
790        self.login()
791        self.browser.open(self.edit_path)
792        # Create a pseudo image file and select it to be uploaded in form
793        pseudo_image = StringIO('I pretend to be a graphics file')
794        ctrl = self.browser.getControl(name='form.passport')
795        file_ctrl = ctrl.mech_control
796        file_ctrl.add_file(pseudo_image, filename='myphoto.jpg')
797        self.browser.getControl("Save").click() # submit form
798        storage = getUtility(IExtFileStore)
799        file_id = IFileStoreNameChooser(self.applicant).chooseName()
800        pseudo_image.seek(0) # reset our file data source
801        self.assertEqual(
802            storage.getFile(file_id).read(), pseudo_image.read())
803        return
804
805    def test_uploaded_image_browsable_wo_errors(self):
806        # We can upload a different image and browse it after submit,
807        # if there are no errors in form
808        self.login()
809        self.browser.open(self.edit_path)
810        self.fill_correct_values() # fill other fields with correct values
811        # Create a pseudo image file and select it to be uploaded in form
812        pseudo_image = StringIO('I pretend to be a graphics file')
813        ctrl = self.browser.getControl(name='form.passport')
814        file_ctrl = ctrl.mech_control
815        file_ctrl.add_file(pseudo_image, filename='myphoto.jpg')
816        self.browser.getControl("Save").click() # submit form
817        # There is a correct <img> link included
818        self.assertTrue(
819            '<img src="passport.jpg" height="180px" />' in self.browser.contents)
820        # Browsing the link shows a real image
821        self.browser.open(self.image_url('passport.jpg'))
822        self.assertEqual(
823            self.browser.headers['content-type'], 'image/jpeg')
824        self.assertEqual(len(self.browser.contents), 31)
825
826    def test_uploaded_image_stored_in_imagestorage_wo_errors(self):
827        # After uploading a new passport pic the file is correctly
828        # stored in an imagestorage if form contains no errors
829        self.login()
830        self.browser.open(self.edit_path)
831        self.fill_correct_values() # fill other fields with correct values
832        # Create a pseudo image file and select it to be uploaded in form
833        pseudo_image = StringIO('I pretend to be a graphics file')
834        ctrl = self.browser.getControl(name='form.passport')
835        file_ctrl = ctrl.mech_control
836        file_ctrl.add_file(pseudo_image, filename='myphoto.jpg')
837        self.browser.getControl("Save").click() # submit form
838        storage = getUtility(IExtFileStore)
839        file_id = IFileStoreNameChooser(self.applicant).chooseName()
840        # The stored image can be fetched
841        fd = storage.getFile(file_id)
842        file_len = len(fd.read())
843        self.assertEqual(file_len, 31)
844        # When an applicant is removed, also the image is gone.
845        del self.app['applicants'][container_name_1][self.applicant.application_number]
846        fd = storage.getFile(file_id)
847        self.assertTrue(fd is None)
848
849    def test_uploaded_images_equal(self):
850        # Make sure uploaded images do really differ if we eject a
851        # change notfication (and do not if we don't)
852        self.login()
853        self.browser.open(self.edit_path)
854        self.fill_correct_values() # fill other fields with correct values
855        self.browser.getControl("Save").click() # submit form
856        # Now go on as an officer
857        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
858        self.browser.open(self.manage_path)
859
860        # Create a pseudo image file and select it to be uploaded in form
861        pseudo_image = StringIO('I pretend to be a graphics file')
862        ctrl = self.browser.getControl(name='form.passport')
863        file_ctrl = ctrl.mech_control
864        file_ctrl.add_file(pseudo_image, filename='myphoto.jpg')
865        file_id = IFileStoreNameChooser(self.applicant).chooseName()
866        setSite(self.app)
867        passport0 = getUtility(IExtFileStore).getFile(file_id)
868        self.browser.getControl("Save").click() # submit form with changed pic
869        passport1 = getUtility(IExtFileStore).getFile(file_id).read()
870        self.browser.getControl("Save").click() # submit form w/o changes
871        passport2 = getUtility(IExtFileStore).getFile(file_id).read()
872        self.assertTrue(passport0 is None)
873        self.assertTrue(passport0 != passport1)
874        self.assertTrue(passport1 == passport2)
875        return
876
877    def test_upload_image_by_manager_with_logging(self):
878        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
879        self.browser.open(self.manage_path)
880        # Create a pseudo image file and select it to be uploaded in form
881        photo_content = 'A' * 1024 * 5  # A string of 5 KB size
882        pseudo_image = StringIO(photo_content)
883        ctrl = self.browser.getControl(name='form.passport')
884        file_ctrl = ctrl.mech_control
885        file_ctrl.add_file(pseudo_image, filename='myphoto.jpg')
886        self.browser.getControl("Save").click() # submit form
887        # Even though the form could not be saved ...
888        self.assertTrue(
889            'Required input is missing' in self.browser.contents)
890        # ... the file has been successfully uploaded
891        logfile = os.path.join(
892            self.app['datacenter'].storage, 'logs', 'applicants.log')
893        logcontent = open(logfile).read()
894        self.assertTrue(
895            'zope.mgr - applicants.browser.ApplicantManageFormPage - '
896            '%s - saved: passport'
897            % (self.applicant.applicant_id)
898            in logcontent)
899
900    def test_application_slip_with_non_jpg_image(self):
901        IWorkflowState(self.applicant).setState('submitted')
902        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
903        self.browser.open(self.manage_path)
904        # Create a pseudo image file and select it to be uploaded in form
905        photo_content = 'A' * 1024 * 5  # A string of 5 KB size
906        pseudo_image = StringIO(photo_content)
907        ctrl = self.browser.getControl(name='form.passport')
908        file_ctrl = ctrl.mech_control
909        file_ctrl.add_file(pseudo_image, filename='myphoto.jpg')
910        self.browser.getControl("Save").click() # submit form
911        self.browser.open(self.manage_path)
912        self.browser.getLink("Download application slip").click()
913        self.assertEqual(self.browser.headers['Status'], '200 Ok')
914        self.assertMatches(
915            '...Your image file is corrupted. Please replace...',
916            self.browser.contents)
917
918    def test_pay_portal_application_fee(self):
919        self.login()
920        self.browser.open(self.edit_path)
921        self.fill_correct_values()
922        # We have to save the form otherwise the filled fields will be cleared
923        # after adding an online payment, because adding an online payment
924        # requires a filled form but does not save it
925        self.browser.getControl("Save").click()
926        # Payment section does not appear if application fee isn't set
927        self.assertFalse('Payment Tickets' in self.browser.contents)
928        self.assertFalse('Add online payment' in self.browser.contents)
929        self.applicantscontainer.application_fee = 200.0
930        self.browser.open(self.edit_path)
931        self.browser.getControl("Add online payment ticket").click()
932        # Session object missing
933        self.assertTrue(
934            'Session configuration object is not available'
935            in self.browser.contents)
936        configuration = SessionConfiguration()
937        configuration.academic_session = session_1
938        self.app['configuration'].addSessionConfiguration(configuration)
939        self.browser.open(self.edit_path)
940        self.browser.getControl("Add online payment ticket").click()
941        self.assertMatches('...Payment ticket created...',
942                           self.browser.contents)
943        self.assertMatches('...Activation Code...',
944                           self.browser.contents)
945        self.assertTrue(
946            '<span>200.0</span>' in self.browser.contents)
947        # Payment ticket can be removed if they haven't received a
948        # valid callback
949        self.browser.open(self.edit_path)
950        ctrl = self.browser.getControl(name='val_id')
951        value = ctrl.options[0]
952        ctrl.getControl(value=value).selected = True
953        self.browser.getControl("Remove selected", index=0).click()
954        self.assertMatches('...Successfully removed...', self.browser.contents)
955        # We will try the callback request view
956        self.browser.getControl("Add online payment ticket").click()
957        self.browser.open(self.edit_path)
958        ctrl = self.browser.getControl(name='val_id')
959        value = ctrl.options[0]
960        self.browser.getLink(value).click()
961        self.assertMatches('...Amount Authorized...',
962                           self.browser.contents)
963        payment_url = self.browser.url
964        payment_id = self.applicant.keys()[0]
965        payment = self.applicant[payment_id]
966        self.assertEqual(payment.p_item,'This is the %s container' % container_name_1)
967        self.assertEqual(payment.p_session, session_1)
968        self.assertEqual(payment.p_category,'application')
969        self.assertEqual(payment.amount_auth,200.0)
970        # Applicant is payer of the payment ticket.
971        self.assertEqual(
972            IPayer(payment).display_fullname, 'John Anthony Tester')
973        self.assertEqual(
974            IPayer(payment).id, self.applicant.applicant_id)
975        self.assertEqual(IPayer(payment).faculty, 'N/A')
976        self.assertEqual(IPayer(payment).department, 'N/A')
977        # The pdf payment slip can't yet be opened
978        #self.browser.open(payment_url + '/payment_receipt.pdf')
979        #self.assertMatches('...Ticket not yet paid...',
980        #                   self.browser.contents)
981        # Approve payment
982        # Applicants can't approve payments
983        self.assertRaises(
984            Unauthorized, self.browser.open, payment_url + '/approve')
985        # We approve the payment by bypassing the view
986        payment.approve()
987        # Applicant is is not yet in state 'paid' because it was only
988        # the payment which we set to paid
989        self.browser.open(self.view_path)
990        self.assertMatches('...started...',
991                           self.browser.contents)
992        self.assertTrue(self.applicant.state == 'started')
993        # Let's logout and approve the payment as manager
994        self.browser.getLink("Logout").click()
995        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
996        # First we reset the payment
997        payment.r_amount_approved = 0.0
998        payment.r_code = u''
999        payment.p_state = 'unpaid'
1000        payment.r_desc = u''
1001        payment.payment_date = None
1002        self.browser.open(payment_url)
1003        self.browser.getLink("Approve payment").click()
1004        self.assertEqual(payment.p_state, 'paid')
1005        self.assertEqual(payment.r_amount_approved, 200.0)
1006        self.assertEqual(payment.r_code, 'AP')
1007        self.assertTrue(self.applicant.state == 'paid')
1008        # Approval is logged in students.log ...
1009        logfile = os.path.join(
1010            self.app['datacenter'].storage, 'logs', 'applicants.log')
1011        logcontent = open(logfile).read()
1012        self.assertTrue(
1013            'zope.mgr - applicants.browser.OnlinePaymentApprovePage - '
1014            '%s - approved' % self.applicant.applicant_id
1015            in logcontent)
1016        # ... and in payments.log
1017        logfile = os.path.join(
1018            self.app['datacenter'].storage, 'logs', 'payments.log')
1019        logcontent = open(logfile).read()
1020        self.assertTrue(
1021            '"zope.mgr",%s,%s,application,200.0,AP,,,,,,\n'
1022            % (self.applicant.applicant_id, payment.p_id)
1023            in logcontent)
1024        # Payment slips can't be downloaded ...
1025        payment_id = self.applicant.keys()[0]
1026        self.browser.open(self.view_path + '/' + payment_id)
1027        self.browser.getLink("Download payment slip").click()
1028        self.assertTrue(
1029            'Please submit the application form before trying to download payment slips.'
1030            in self.browser.contents)
1031        # ... unless form is submitted.
1032        self.browser.open(self.view_path + '/edit')
1033        image = open(SAMPLE_IMAGE, 'rb')
1034        ctrl = self.browser.getControl(name='form.passport')
1035        file_ctrl = ctrl.mech_control
1036        file_ctrl.add_file(image, filename='myphoto.jpg')
1037        self.browser.getControl(name="confirm_passport").value = True
1038        self.browser.getControl("Finally Submit").click()
1039        self.browser.open(self.view_path + '/' + payment_id)
1040        self.browser.getLink("Download payment slip").click()
1041        self.assertEqual(self.browser.headers['Content-Type'],
1042                 'application/pdf')
1043        return
1044
1045    def prepare_special_container(self):
1046        # Add special application container
1047        container_name = u'special%s' % session_1
1048        applicantscontainer = ApplicantsContainer()
1049        applicantscontainer.code = container_name
1050        applicantscontainer.prefix = 'special'
1051        applicantscontainer.year = session_1
1052        applicantscontainer.title = u'This is a special app container'
1053        applicantscontainer.application_category = 'no'
1054        applicantscontainer.mode = 'create'
1055        applicantscontainer.strict_deadline = True
1056        delta = timedelta(days=10)
1057        applicantscontainer.startdate = datetime.now(pytz.utc) - delta
1058        applicantscontainer.enddate = datetime.now(pytz.utc) + delta
1059        self.app['applicants'][container_name] = applicantscontainer
1060        # Add an applicant
1061        applicant = createObject('waeup.Applicant')
1062        # reg_number is the only field which has to be preset here
1063        # because managers are allowed to edit this required field
1064        applicant.reg_number = u'12345'
1065        self.special_applicant = applicant
1066        self.app['applicants'][container_name].addApplicant(applicant)
1067        IUserAccount(
1068            self.app['applicants'][container_name][
1069            applicant.application_number]).setPassword('apwd')
1070        # Add session configuration object
1071        self.configuration = SessionConfiguration()
1072        self.configuration.academic_session = session_1
1073        #self.configuration.transcript_fee = 200.0
1074        self.configuration.clearance_fee = 300.0
1075        self.app['configuration'].addSessionConfiguration(self.configuration)
1076
1077
1078    def test_pay_special_fee(self):
1079        self.prepare_special_container()
1080        # Login
1081        self.browser.open(self.login_path)
1082        self.browser.getControl(
1083            name="form.login").value = self.special_applicant.applicant_id
1084        self.browser.getControl(name="form.password").value = 'apwd'
1085        self.browser.getControl("Login").click()
1086        applicant_path = self.browser.url
1087        self.browser.getLink("Edit application record").click()
1088        self.browser.getControl(name="form.firstname").value = 'John'
1089        self.browser.getControl(name="form.middlename").value = 'Anthony'
1090        self.browser.getControl(name="form.lastname").value = 'Tester'
1091        self.browser.getControl(name="form.special_application").value = [
1092            'transcript']
1093        self.browser.getControl(name="form.date_of_birth").value = '09/09/1988'
1094        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
1095        self.configuration.transcript_fee = 0.0
1096        self.browser.getControl("Save").click()
1097        self.browser.getControl("Add online payment ticket").click()
1098        self.assertMatches('...Amount could not be determined...',
1099                           self.browser.contents)
1100        self.configuration.transcript_fee = 200.0
1101        self.browser.getLink("Edit application record").click()
1102        self.browser.getControl("Add online payment ticket").click()
1103        self.assertMatches('...Payment ticket created...',
1104                           self.browser.contents)
1105        self.assertTrue(
1106            '<span>Transcript Fee</span>' in self.browser.contents)
1107        self.assertTrue(
1108            'This is a special app container' in self.browser.contents)
1109        self.assertTrue(
1110            '<span>200.0</span>' in self.browser.contents)
1111        self.assertEqual(len(self.special_applicant.keys()), 1)
1112        # The applicant's workflow state is paid ...
1113        self.special_applicant.payments[0].approveApplicantPayment()
1114        self.assertEqual(self.special_applicant.state, 'paid')
1115        self.browser.open(applicant_path + '/edit')
1116        # ... but he can create further tickets.
1117        self.browser.getControl(name="form.special_application").value = [
1118            'clearance']
1119        self.browser.getControl("Save").click()
1120        self.browser.getControl("Add online payment ticket").click()
1121        self.assertMatches('...Payment ticket created...',
1122                           self.browser.contents)
1123        self.browser.open(applicant_path)
1124        self.assertTrue(
1125            '<td>Acceptance Fee</td>' in self.browser.contents)
1126        self.assertEqual(len(self.special_applicant.keys()), 2)
1127        # Second payment can also be approved wthout error message
1128        flashtype, msg, log = self.special_applicant.payments[1].approveApplicantPayment()
1129        self.assertEqual(flashtype, 'success')
1130        self.assertEqual(msg, 'Payment approved')
1131        # Payment slips can't be downloaded ...
1132        payment_id = self.special_applicant.keys()[0]
1133        self.browser.open(applicant_path + '/' + payment_id)
1134        self.browser.getLink("Download payment slip").click()
1135        self.assertTrue(
1136            'Please submit the application form before trying to download payment slips.'
1137            in self.browser.contents)
1138        # ... unless form is submitted.
1139        self.browser.open(applicant_path + '/edit')
1140        image = open(SAMPLE_IMAGE, 'rb')
1141        ctrl = self.browser.getControl(name='form.passport')
1142        file_ctrl = ctrl.mech_control
1143        file_ctrl.add_file(image, filename='myphoto.jpg')
1144        self.browser.getControl(name="confirm_passport").value = True
1145        self.browser.getControl("Finally Submit").click()
1146        self.browser.open(applicant_path + '/' + payment_id)
1147        self.browser.getLink("Download payment slip").click()
1148        self.assertEqual(self.browser.headers['Content-Type'],
1149                 'application/pdf')
1150        return
1151
1152    def test_final_submit(self):
1153        # Make sure that a correctly filled form with passport picture
1154        # can be submitted (only) after payment
1155        self.login()
1156        self.browser.getLink("Edit application record").click()
1157        self.assertFalse('Finally Submit' in self.browser.contents)
1158        IWorkflowInfo(self.applicant).fireTransition('pay')
1159        self.browser.open(self.edit_path)
1160        self.assertTrue('Finally Submit' in self.browser.contents)
1161        self.fill_correct_values() # fill other fields with correct values
1162        self.browser.getControl("Save").click()
1163        self.browser.getControl("Finally Submit").click()
1164        # We forgot to upload a passport picture
1165        self.assertTrue(
1166            'No passport picture uploaded' in self.browser.contents)
1167        # Use a real image file and select it to be uploaded in form
1168        image = open(SAMPLE_IMAGE, 'rb')
1169        ctrl = self.browser.getControl(name='form.passport')
1170        file_ctrl = ctrl.mech_control
1171        file_ctrl.add_file(image, filename='myphoto.jpg')
1172        self.browser.getControl("Finally Submit").click() # (finally) submit form
1173        # The picture has been uploaded but the form cannot be submitted
1174        # since the passport confirmation box was not ticked
1175        self.assertTrue(
1176            'Passport picture confirmation box not ticked'
1177            in self.browser.contents)
1178        self.browser.getControl(name="confirm_passport").value = True
1179        # If application period has expired and strict-deadline is set
1180        # applicants do notsee edit button and can't open
1181        # the edit form.
1182        self.applicantscontainer.enddate = datetime.now(pytz.utc)
1183        self.browser.open(self.view_path)
1184        self.assertFalse(
1185            'Edit application record' in self.browser.contents)
1186        self.browser.open(self.edit_path)
1187        self.assertTrue(
1188            'form is locked' in self.browser.contents)
1189        # We can either postpone the enddate ...
1190        self.applicantscontainer.enddate = datetime.now(
1191            pytz.utc) + timedelta(days=10)
1192        self.browser.open(self.edit_path)
1193        self.browser.getControl(name="confirm_passport").value = True
1194        self.browser.getControl("Finally Submit").click()
1195        self.assertTrue(
1196            'Application submitted' in self.browser.contents)
1197        # ... or allow submission after deadline.
1198        IWorkflowState(self.applicant).setState('paid')
1199        self.applicant.locked = False
1200        self.applicantscontainer.strict_deadline = False
1201        self.browser.open(self.edit_path)
1202        self.browser.getControl(name="confirm_passport").value = True
1203        self.browser.getControl("Finally Submit").click()
1204        self.assertTrue(
1205            'Application submitted' in self.browser.contents)
1206        return
1207
1208    def test_locking(self):
1209        # Make sure that locked forms can't be submitted
1210        self.login()
1211        self.browser.open(self.edit_path)
1212        self.fill_correct_values() # fill other fields with correct values
1213        # Create a pseudo image file and select it to be uploaded in form
1214        pseudo_image = StringIO('I pretend to be a graphics file')
1215        ctrl = self.browser.getControl(name='form.passport')
1216        file_ctrl = ctrl.mech_control
1217        file_ctrl.add_file(pseudo_image, filename='myphoto.jpg')
1218        self.browser.getControl("Save").click()
1219        # Now we lock the form
1220        self.applicant.locked = True
1221        self.browser.open(self.edit_path)
1222        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1223        self.assertTrue(
1224            'The requested form is locked' in self.browser.contents)
1225        return
1226
1227    def test_certificate_removed(self):
1228        self.login()
1229        self.browser.open(self.edit_path)
1230        self.fill_correct_values()
1231        self.browser.getControl("Save").click()
1232        self.browser.open(self.view_path)
1233        self.assertTrue(
1234            'Unnamed Certificate' in self.browser.contents)
1235        self.browser.open(self.edit_path)
1236        self.assertTrue(
1237            '<option selected="selected" value="CERT1">' in self.browser.contents)
1238        # Now we remove the certificate
1239        del self.app['faculties']['fac1']['dep1'].certificates['CERT1']
1240        # The certificate is still shown in display mode
1241        self.browser.open(self.view_path)
1242        self.assertTrue(
1243            'Unnamed Certificate' in self.browser.contents)
1244        # The certificate is still selectable in edit mode so that it won't
1245        # be automatically replaced by another (arbitrary) certificate
1246        self.browser.open(self.edit_path)
1247        self.assertTrue(
1248            '<option selected="selected" value="CERT1">' in self.browser.contents)
1249        # Consequently, the certificate is still shown after saving the form
1250        self.browser.getControl("Save").click()
1251        self.browser.open(self.view_path)
1252        self.assertTrue(
1253            'Unnamed Certificate' in self.browser.contents)
1254        # Even if we add a new certificate the previous (removed)
1255        # certificate is shown
1256        certificate = createObject('waeup.Certificate')
1257        certificate.code = 'CERT2'
1258        certificate.title = 'New Certificate'
1259        certificate.application_category = 'basic'
1260        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
1261            certificate)
1262        self.browser.open(self.edit_path)
1263        self.assertTrue(
1264            '<option selected="selected" value="CERT1">'
1265            in self.browser.contents)
1266
1267class ApplicantRegisterTests(ApplicantsFullSetup):
1268    # Tests for applicant registration
1269
1270    layer = FunctionalLayer
1271
1272    def test_register_applicant_create(self):
1273        config = grok.getSite()['configuration']
1274        config.maintmode_enabled_by = u'any_user'
1275        self.assertEqual(len(self.app['applicants'][container_name_1]), 1)
1276        # An applicant can register himself.
1277        self.browser.open(self.container_path)
1278        self.browser.getLink("Register for application").click()
1279        self.assertTrue(
1280            'The portal is in maintenance mode' in self.browser.contents)
1281        config.maintmode_enabled_by = None
1282        self.browser.getLink("Register for application").click()
1283        # The edit form now opens and can be filled with suitable values
1284        self.browser.getControl(name="form.firstname").value = 'Anna'
1285        self.browser.getControl(name="form.lastname").value = 'Kurios'
1286        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
1287        self.browser.getControl(name="form.phone.country").value = ['+234']
1288        self.browser.getControl(name="form.phone.area").value = '555'
1289        self.browser.getControl(name="form.phone.ext").value = '6666666'
1290        self.browser.getControl("Send login credentials").click()
1291        self.assertEqual(self.browser.url,
1292            self.container_path + '/registration_complete?email=xx%40yy.zz')
1293        # A new applicant has been created
1294        self.assertEqual(len(self.app['applicants'][container_name_1]), 2)
1295        # The new applicant can be found in the catalog via the email address
1296        cat = getUtility(ICatalog, name='applicants_catalog')
1297        results = list(
1298            cat.searchResults(email=('xx@yy.zz', 'xx@yy.zz')))
1299        applicant = results[0]
1300        self.assertEqual(applicant.lastname,'Kurios')
1301        # The application_id has been copied to the reg_number
1302        self.assertEqual(applicant.applicant_id, applicant.reg_number)
1303        # The applicant can be found in the catalog via the reg_number
1304        results = list(
1305            cat.searchResults(
1306            reg_number=(applicant.reg_number, applicant.reg_number)))
1307        self.assertEqual(applicant,results[0])
1308        return
1309
1310    def test_register_applicant_take_unused_record(self):
1311        # Create an unused record
1312        uu_applicant = createObject('waeup.Applicant')
1313        self.app['applicants'][container_name_1].addApplicant(uu_applicant)
1314        self.assertEqual(uu_applicant.container_code, container_name_1 + '-')
1315        self.assertEqual(len(self.app['applicants'][container_name_1]), 2)
1316        self.browser.open(self.container_path)
1317        self.browser.getLink("Register for application").click()
1318        # Fill the edit form with suitable values
1319        self.browser.getControl(name="form.firstname").value = 'Anna'
1320        self.browser.getControl(name="form.lastname").value = 'Kurios'
1321        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
1322        self.browser.getControl(name="form.phone.country").value = ['+234']
1323        self.browser.getControl(name="form.phone.area").value = '555'
1324        self.browser.getControl(name="form.phone.ext").value = '6666666'
1325        self.browser.getControl("Send login credentials").click()
1326        # No applicant has been created ...
1327        self.assertEqual(len(self.app['applicants'][container_name_1]), 2)
1328        # ... and the existing, formerly unused record has been used instead
1329        self.assertEqual(uu_applicant.lastname, 'Kurios')
1330        self.assertEqual(uu_applicant.container_code, container_name_1 + '+')
1331        return
1332
1333    def test_register_applicant_update(self):
1334        # We change the application mode and check if applicants
1335        # can find and update imported records instead of creating new records.
1336        # First we check what happens if record does not exist.
1337        self.applicantscontainer.mode = 'update'
1338        self.browser.open(self.container_path + '/register')
1339        self.browser.getControl(name="form.lastname").value = 'Better'
1340        self.browser.getControl(name="form.reg_number").value = 'anynumber'
1341        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
1342        self.browser.getControl("Send login credentials").click()
1343        self.assertTrue('No application record found.'
1344            in self.browser.contents)
1345        # Even with the correct reg_number we can't register
1346        # because lastname attribute is not set.
1347        self.applicantscontainer.mode = 'update'
1348        self.browser.open(self.container_path + '/register')
1349        self.browser.getControl(name="form.lastname").value = 'Better'
1350        self.browser.getControl(name="form.reg_number").value = '1234'
1351        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
1352        self.browser.getControl("Send login credentials").click()
1353        self.assertTrue('An error occurred.' in self.browser.contents)
1354        # Let's set this attribute manually
1355        # and try to register with a wrong name.
1356        self.applicant.lastname = u'Better'
1357        self.browser.open(self.container_path + '/register')
1358        self.browser.getControl(name="form.lastname").value = 'Worse'
1359        self.browser.getControl(name="form.reg_number").value = '1234'
1360        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
1361        self.browser.getControl("Send login credentials").click()
1362        # Anonymous is not informed that lastname verification failed.
1363        # It seems that the record doesn't exist.
1364        self.assertTrue('No application record found.'
1365            in self.browser.contents)
1366        # Even with the correct lastname we can't register if a
1367        # password has been set and used.
1368        IWorkflowState(self.applicant).setState('started')
1369        self.browser.getControl(name="form.lastname").value = 'Better'
1370        self.browser.getControl(name="form.reg_number").value = '1234'
1371        self.browser.getControl("Send login credentials").click()
1372        self.assertTrue('Your password has already been set and used.'
1373            in self.browser.contents)
1374        #IUserAccount(
1375        #    self.app['applicants'][container_name_1][
1376        #    self.applicant.application_number]).context.password = None
1377        # Even without unsetting the password we can re-register if state
1378        # is 'initialized'
1379        IWorkflowState(self.applicant).setState('initialized')
1380        self.browser.open(self.container_path + '/register')
1381        # The lastname field, used for verification, is not case-sensitive.
1382        self.browser.getControl(name="form.lastname").value = 'bEtter'
1383        self.browser.getControl(name="form.reg_number").value = '1234'
1384        self.browser.getControl(name="form.email").value = 'new@yy.zz'
1385        self.browser.getControl("Send login credentials").click()
1386        # Yeah, we succeded ...
1387        self.assertTrue('Your registration was successful.'
1388            in self.browser.contents)
1389        # ... and  applicant can be found in the catalog via the email address
1390        cat = getUtility(ICatalog, name='applicants_catalog')
1391        results = list(
1392            cat.searchResults(
1393            email=('new@yy.zz', 'new@yy.zz')))
1394        self.assertEqual(self.applicant,results[0])
1395        return
1396
1397    def test_change_password_request(self):
1398        self.browser.open('http://localhost/app/changepw')
1399        self.browser.getControl(name="form.identifier").value = '1234'
1400        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
1401        self.browser.getControl("Send login credentials").click()
1402        self.assertTrue('No record found' in self.browser.contents)
1403        self.applicant.email = 'aa@aa.ng'
1404        # Update the catalog
1405        notify(grok.ObjectModifiedEvent(self.applicant))
1406        self.browser.open('http://localhost/app/changepw')
1407        self.browser.getControl(name="form.identifier").value = '1234'
1408        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
1409        self.browser.getControl("Send login credentials").click()
1410        self.assertTrue(
1411            'An email with your user name and password has been sent'
1412            in self.browser.contents)
1413
1414    def test_check_status(self):
1415        self.applicant.lastname = u'Lion '
1416        self.browser.open('http://localhost/app/applicants/checkstatus')
1417        self.browser.getControl(name="unique_id").value = 'nonsense'
1418        self.browser.getControl(name="lastname").value = 'Lion'
1419        self.browser.getControl("Submit").click()
1420        self.assertTrue('No application record found' in self.browser.contents)
1421        self.browser.getControl(
1422          name="unique_id").value = self.applicant.applicant_id
1423        self.browser.getControl(name="lastname").value = 'nonsense'
1424        self.browser.getControl("Submit").click()
1425        self.assertTrue('No application record found' in self.browser.contents)
1426        self.browser.getControl(
1427          name="unique_id").value = self.applicant.applicant_id
1428        self.browser.getControl(name="lastname").value = 'Lion'
1429        self.browser.getControl("Submit").click()
1430        self.assertTrue('Admission status of' in self.browser.contents)
1431        self.assertTrue(
1432          'You have not yet submitted your application' in self.browser.contents)
1433        IWorkflowState(self.applicant).setState('admitted')
1434        self.browser.open('http://localhost/app/applicants/checkstatus')
1435        self.browser.getControl(
1436          name="unique_id").value = self.applicant.applicant_id
1437        # Whitespaces are ignored.
1438        self.browser.getControl(name="lastname").value = 'Lion'
1439        self.browser.getControl("Submit").click()
1440        self.assertTrue('Congratulations!' in self.browser.contents)
1441        self.assertFalse('Study Course' in self.browser.contents)
1442        self.applicant.course_admitted = self.certificate
1443        self.browser.open('http://localhost/app/applicants/checkstatus')
1444        self.browser.getControl(
1445          name="unique_id").value = self.applicant.applicant_id
1446        self.browser.getControl(name="lastname").value = 'Lion'
1447        self.browser.getControl("Submit").click()
1448        self.assertTrue('Congratulations!' in self.browser.contents)
1449        self.assertTrue('Unnamed Certificate (CERT1)' in self.browser.contents)
1450        self.assertTrue(
1451          'Department of Unnamed Department (dep1)' in self.browser.contents)
1452        self.assertTrue(
1453          'Faculty of Unnamed Faculty (NA)' in self.browser.contents)
1454        # Also the reg_number can be used and page shows student id and password
1455        # if applicant is in state created.
1456        IWorkflowState(self.applicant).setState('created')
1457        self.applicant.student_id = u'my id'
1458        self.browser.open('http://localhost/app/applicants/checkstatus')
1459        self.browser.getControl(
1460          name="unique_id").value = self.applicant.reg_number
1461        self.browser.getControl(name="lastname").value = 'Lion'
1462        self.browser.getControl("Submit").click()
1463        self.assertTrue('Congratulations!' in self.browser.contents)
1464        self.assertTrue('Unnamed Certificate (CERT1)' in self.browser.contents)
1465        self.assertTrue('Department of Unnamed Department (dep1)'
1466            in self.browser.contents)
1467        self.assertTrue(
1468          'Faculty of Unnamed Faculty (NA)' in self.browser.contents)
1469        self.assertTrue('user name (= student id) is: <strong>my id</strong>'
1470            in self.browser.contents)
1471        self.assertTrue(
1472          'password is: <strong>%s</strong>' % self.applicant.application_number
1473          in self.browser.contents)
1474       
1475
1476class ApplicantsExportTests(ApplicantsFullSetup, FunctionalAsyncTestCase):
1477    # Tests for StudentsContainer class views and pages
1478
1479    layer = FunctionalLayer
1480
1481    def wait_for_export_jobs_completed(self):
1482        # helper function waiting until the current export job is completed
1483        manager = getUtility(IJobManager)
1484        job_ids = [i[0] for i in self.app['datacenter'].running_exports]
1485        jobs = [manager.get(job_id) for job_id in job_ids]
1486        for job in jobs:
1487            wait_for_result(job)
1488        return job_ids
1489
1490    def test_applicants_in_container_export(self):
1491        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1492        container_path = 'http://localhost/app/applicants/%s' % container_name_1
1493        self.browser.open(container_path)
1494        self.browser.getLink("Export application data").click()
1495        self.browser.getControl("Start new exports").click()
1496        job_ids = self.wait_for_export_jobs_completed()
1497        # Two exports were created
1498        self.assertEqual(len(self.app['datacenter'].running_exports), 2)
1499       # When the jobs are finished and we reload the page...
1500        self.browser.open(container_path + '/exports')
1501        # ... the both csv files can be downloaded ...
1502        self.browser.getLink("Download", index=0).click()
1503        self.assertEqual(self.browser.headers['content-type'],
1504            'text/csv; charset=UTF-8')
1505        self.assertTrue(
1506            'filename="WAeUP.Kofa_applicants_%s.csv' % job_ids[0] in
1507            self.browser.headers['content-disposition'])
1508        self.browser.open(container_path + '/exports')
1509        self.browser.getLink("Download", index=1).click()
1510        self.assertEqual(self.browser.headers['content-type'],
1511            'text/csv; charset=UTF-8')
1512        self.assertTrue(
1513            'filename="WAeUP.Kofa_applicantpayments_%s.csv' % job_ids[1] in
1514            self.browser.headers['content-disposition'])
1515        # ... and discarded
1516        self.browser.open(container_path + '/exports')
1517        self.browser.getControl("Discard", index=0).click()
1518        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
1519        self.browser.getControl("Discard").click()
1520        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
1521        # Creation, downloading and discarding are logged
1522        logfile = os.path.join(
1523            self.app['datacenter'].storage, 'logs', 'datacenter.log')
1524        logcontent = open(logfile).read()
1525        self.assertTrue(
1526            'zope.mgr - applicants.browser.ExportJobContainerJobStart - '
1527            'exported: applicants (%s), job_id=%s'
1528            % (container_name_1, job_ids[0]) in logcontent
1529            )
1530        self.assertTrue(
1531            'zope.mgr - applicants.browser.ExportJobContainerDownload '
1532            '- downloaded: WAeUP.Kofa_applicants_%s.csv, job_id=%s'
1533            % (job_ids[0], job_ids[0]) in logcontent
1534            )
1535        self.assertTrue(
1536            'zope.mgr - applicants.browser.ExportJobContainerOverview '
1537            '- discarded: job_id=%s' % job_ids[0] in logcontent
1538            )
1539        self.assertTrue(
1540            'zope.mgr - applicants.browser.ExportJobContainerJobStart - '
1541            'exported: applicantpayments (%s), job_id=%s'
1542            % (container_name_1, job_ids[1]) in logcontent
1543            )
1544        self.assertTrue(
1545            'zope.mgr - applicants.browser.ExportJobContainerDownload '
1546            '- downloaded: WAeUP.Kofa_applicantpayments_%s.csv, job_id=%s'
1547            % (job_ids[1], job_ids[1]) in logcontent
1548            )
1549        self.assertTrue(
1550            'zope.mgr - applicants.browser.ExportJobContainerOverview '
1551            '- discarded: job_id=%s' % job_ids[1] in logcontent
1552            )
1553
1554class ApplicantRefereeReportTests(ApplicantsFullSetup, FunctionalAsyncTestCase):
1555    # Tests for ApplicantRefereeReport class views and pages
1556
1557    layer = FunctionalLayer
1558
1559    def setUp(self):
1560        super(ApplicantRefereeReportTests, self).setUp()
1561        self.setup_logging()
1562        return
1563
1564    def tearDown(self):
1565        super(ApplicantRefereeReportTests, self).tearDown()
1566        self.teardown_logging()
1567        return
1568
1569    def setup_logging(self):
1570        # setup a log-handler that catches all fake mailer output
1571        self.stream = StringIO()
1572        handler = logging.StreamHandler(self.stream)
1573        logger = logging.getLogger('test.smtp')
1574        logger.addHandler(handler)
1575        logger.setLevel(logging.INFO)
1576        return
1577
1578    def get_fake_smtp_output(self):
1579        # get output generated by fake mailer
1580        self.stream.flush()
1581        self.stream.seek(0)
1582        return self.stream.read()
1583
1584    def teardown_logging(self):
1585        # remove the log handler for fake mailer output
1586        logger = logging.getLogger('test.smtp')
1587        handlers = [x for x in logger.handlers]
1588        for handler in handlers:
1589            logger.removeHandler(handler)
1590        return
1591
1592    def test_refereereport_mandate(self):
1593        mandate = RefereeReportMandate()
1594        mandate.params['name'] = u'John Referee'
1595        mandate.params['email'] = 'aa@aa.aa'
1596        mandate.params[
1597            'redirect_path'] = '/applicants/%s/%s/addrefereereport' % (
1598                container_name_1, self.applicant.application_number)
1599        self.app['mandates'].addMandate(mandate)
1600        # Let's open the add form page via the mandate view
1601        self.browser.open('http://localhost/app/mandate?mandate_id=%s'
1602            % mandate.mandate_id)
1603        # Form page opens and is prefilled
1604        self.assertEqual(
1605            self.browser.url,
1606            'http://localhost/app/applicants/%s/%s/addrefereereport?mandate_id=%s'
1607            % (container_name_1, self.applicant.application_number,
1608            mandate.mandate_id))
1609        self.assertTrue('value="John Referee"' in self.browser.contents)
1610        # Let's open the page directly with an invalid mandate
1611        self.browser.open(
1612            'http://localhost/app/applicants/%s/%s/addrefereereport?mandate_id=wrongmadate'
1613            % (container_name_1, self.applicant.application_number))
1614        self.assertTrue('<div class="alert alert-warning">No mandate.</div>'
1615            in self.browser.contents)
1616        self.assertEqual(self.browser.url, 'http://localhost/app')
1617        # Page is also blocked in maintenance mode
1618        grok.getSite()['configuration'].maintmode_enabled_by = u'anybody'
1619        self.browser.open(
1620            'http://localhost/app/applicants/%s/%s/addrefereereport?mandate_id=%s'
1621            % (container_name_1, self.applicant.application_number,
1622            mandate.mandate_id))
1623        self.assertTrue('<div class="alert alert-warning">The portal is '
1624                        'in maintenance mode'
1625            in self.browser.contents)
1626        self.assertEqual(self.browser.url, 'http://localhost/app')
1627
1628    def test_add_and_view_reports(self):
1629        mandate = RefereeReportMandate()
1630        mandate.params['name'] = u'John Referee'
1631        mandate.params['email'] = 'aa@aa.aa'
1632        mandate.params[
1633            'redirect_path'] = '/applicants/%s/%s/addrefereereport' % (
1634                container_name_1, self.applicant.application_number)
1635        self.app['mandates'].addMandate(mandate)
1636        self.assertEqual(len(self.app['mandates'].keys()), 1)
1637        # Let's open the add form page via the mandate view
1638        self.browser.open('http://localhost/app/mandate?mandate_id=%s'
1639            % mandate.mandate_id)
1640        self.assertTrue('Joan None' in self.browser.contents)
1641        # Report can't be saved without required fields
1642        self.browser.getControl(name="form.email").value = ''
1643        self.browser.getControl("Submit").click()
1644        self.assertTrue('Required input is missing' in self.browser.contents)
1645        self.browser.getControl(name="form.email").value = 'bb@bb.bb'
1646        self.browser.getControl("Submit").click()
1647        self.assertTrue('Referee report has been saved' in self.browser.contents)
1648        self.assertEqual(self.browser.url, 'http://localhost/app')
1649        # Report has been created
1650        self.assertEqual(len(self.applicant.refereereports), 1)
1651        report = self.applicant.refereereports[0]
1652        # Managers can view the report
1653        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1654        self.browser.open(self.manage_path)
1655        self.browser.getLink("%s" % report.r_id).click()
1656        self.assertEqual(
1657            self.browser.url, self.view_path  + '/%s' % report.r_id)
1658        self.assertTrue('John Referee' in self.browser.contents)
1659        # Report creation is logged
1660        logfile = os.path.join(
1661            self.app['datacenter'].storage, 'logs', 'applicants.log')
1662        logcontent = open(logfile).read()
1663        self.assertTrue(
1664            'zope.anybody - applicants.browser.RefereeReportAddFormPage - '
1665            '%s - added: %s\n' % (self.applicant.applicant_id, report.r_id)
1666            in logcontent
1667            )
1668        # Mandate is deleted
1669        self.assertEqual(len(self.app['mandates'].keys()), 0)
1670
1671    def test_final_submit_with_referees(self):
1672        # Add two referees
1673        referee1 = RefereeEntry()
1674        referee2 = RefereeEntry()
1675        referee1.name = u'Linda Tree'
1676        referee1.email = 'linda@forest.de'
1677        referee2.name = u'Otis Stone'
1678        referee2.email = 'otis@stones.de'
1679        self.applicant.referees = [referee1, referee2]
1680        self.assertFalse(referee1.email_sent)
1681        self.assertFalse(referee2.email_sent)
1682        self.login()
1683        IWorkflowInfo(self.applicant).fireTransition('pay')
1684        self.browser.open(self.edit_path)
1685        self.fill_correct_values() # fill other fields with correct values
1686        image = open(SAMPLE_IMAGE, 'rb')
1687        ctrl = self.browser.getControl(name='form.passport')
1688        file_ctrl = ctrl.mech_control
1689        file_ctrl.add_file(image, filename='myphoto.jpg')
1690        self.browser.getControl("Save").click()
1691        self.browser.getControl(name="confirm_passport").value = True
1692        self.browser.getControl("Finally Submit").click()
1693        mandate_id_0 = self.app['mandates'].keys()[0]
1694        mandate_id_1 = self.app['mandates'].keys()[1]
1695        if self.app['mandates'].values()[0].params['name'] == 'Linda Tree':
1696            mandate_id_0 = self.app['mandates'].keys()[0]
1697            mandate_id_1 = self.app['mandates'].keys()[1]
1698        else:
1699            mandate_id_0 = self.app['mandates'].keys()[1]
1700            mandate_id_1 = self.app['mandates'].keys()[0]
1701        self.assertMatches(
1702            'Sending email from no-reply@waeup.org to linda@forest.de:'
1703            '\nMessage:'
1704            '\nmsg: MIME-Version: 1.0\nmsg: Content-Type: text/plain; charset="us-ascii"'
1705            '\nmsg: Content-Transfer-Encoding: 7bit'
1706            '\nmsg: From: Administrator <no-reply@waeup.org>'
1707            '\nmsg: To: Linda Tree <linda@forest.de>'
1708            '\nmsg: Reply-To: Administrator <contact@waeup.org>'
1709            '\nmsg: Subject: Request for referee report from Sample University'
1710            '\nmsg: '
1711            '\nmsg: Dear Linda Tree,'
1712            '\nmsg: '
1713            '\nmsg: The candidate with Id app%s_372052 and name John Anthony Tester applied to'
1714            '\nmsg: the Sample University to study Unnamed Certificate for the %s/%s session.'
1715            '\nmsg: The candidate has listed you as referee. You are thus required to kindly use'
1716            '\nmsg: the link below to provide your referral remarks on or before'
1717            '\nmsg: 2016-08-12 08:32:41.619671+00:00.'
1718            '\nmsg: '
1719            '\nmsg: Report link: http://localhost/app/mandate?mandate_id=%s'
1720            '\nmsg: '
1721            '\nmsg: Thank You'
1722            '\nmsg: '
1723            '\nmsg: The Secretary'
1724            '\nmsg: Post Graduate School'
1725            '\nmsg: Sample University'
1726            '\nmsg: '
1727            '\nSending email from no-reply@waeup.org to otis@stones.de:'
1728            '\nMessage:'
1729            '\nmsg: MIME-Version: 1.0'
1730            '\nmsg: Content-Type: text/plain; charset="us-ascii"'
1731            '\nmsg: Content-Transfer-Encoding: 7bit'
1732            '\nmsg: From: Administrator <no-reply@waeup.org>'
1733            '\nmsg: To: Otis Stone <otis@stones.de>'
1734            '\nmsg: Reply-To: Administrator <contact@waeup.org>'
1735            '\nmsg: Subject: Request for referee report from Sample University'
1736            '\nmsg: '
1737            '\nmsg: Dear Otis Stone,'
1738            '\nmsg: '
1739            '\nmsg: The candidate with Id app%s_<6-DIGITS> and name John Anthony Tester applied to'
1740            '\nmsg: the Sample University to study Unnamed Certificate for the %s/%s session.'
1741            '\nmsg: The candidate has listed you as referee. You are thus required to kindly use'
1742            '\nmsg: the link below to provide your referral remarks on or before'
1743            '\nmsg: <YYYY-MM-DD hh:mm:ss>.<6-DIGITS>+00:00.'
1744            '\nmsg: '
1745            '\nmsg: Report link: http://localhost/app/mandate?mandate_id=%s'
1746            '\nmsg: '
1747            '\nmsg: Thank You'
1748            '\nmsg: '
1749            '\nmsg: The Secretary'
1750            '\nmsg: Post Graduate School'
1751            '\nmsg: Sample University'
1752            '\nmsg: '
1753            % (session_1, session_1, session_2, mandate_id_0,
1754               session_1, session_1, session_2, mandate_id_1,),
1755            self.get_fake_smtp_output()
1756            )
1757        self.assertTrue(
1758            'Application submitted' in self.browser.contents)
1759        self.assertTrue(
1760            'Form has been successfully submitted and 2 '
1761            'invitation emails were sent.' in self.browser.contents)
1762        logfile = os.path.join(
1763            self.app['datacenter'].storage, 'logs', 'applicants.log')
1764        logcontent = open(logfile).read()
1765        self.assertTrue(
1766            '%s - applicants.browser.ApplicantEditFormPage - %s - '
1767            'email sent: otis@stones.de' %
1768            (self.applicant.applicant_id, self.applicant.applicant_id)
1769            in logcontent)
1770        self.assertTrue(referee1.email_sent)
1771        self.assertTrue(referee2.email_sent)
1772        # If the form is being resubmitted, no more emails will be sent
1773        IWorkflowState(self.applicant).setState('paid')
1774        self.applicant.locked = False
1775        self.browser.open(self.edit_path)
1776        self.browser.getControl(name="confirm_passport").value = True
1777        self.browser.getControl("Finally Submit").click()
1778        self.assertTrue(
1779            'Form has been successfully submitted and 0 '
1780            'invitation emails were sent.' in self.browser.contents)
1781        return
Note: See TracBrowser for help on using the repository browser.