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

Last change on this file since 16282 was 16243, checked in by Henrik Bettermann, 4 years ago

Save email address provided by mandate when referee report
is created. Add RefereeReportManageFormPage (no button available).

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