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

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

Extend IPayer adapter.

  • Property svn:keywords set to Id
File size: 103.3 KB
Line 
1## $Id: test_browser.py 16526 2021-07-05 15:42:14Z 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(IPayer(payment).payer, self.applicant)
1021        self.assertEqual(
1022            IPayer(payment).display_fullname, 'John Anthony Tester')
1023        self.assertEqual(
1024            IPayer(payment).id, self.applicant.applicant_id)
1025        self.assertEqual(IPayer(payment).faculty, 'N/A')
1026        self.assertEqual(IPayer(payment).department, 'N/A')
1027        # The pdf payment slip can't yet be opened
1028        #self.browser.open(payment_url + '/payment_receipt.pdf')
1029        #self.assertMatches('...Ticket not yet paid...',
1030        #                   self.browser.contents)
1031        # Approve payment
1032        # Applicants can't approve payments
1033        self.assertRaises(
1034            Unauthorized, self.browser.open, payment_url + '/approve')
1035        # We approve the payment by bypassing the view
1036        payment.approve()
1037        # Applicant is is not yet in state 'paid' because it was only
1038        # the payment which we set to paid
1039        self.browser.open(self.view_path)
1040        self.assertMatches('...started...',
1041                           self.browser.contents)
1042        self.assertTrue(self.applicant.state == 'started')
1043        # Let's logout and approve the payment as manager
1044        self.browser.getLink("Logout").click()
1045        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1046        # First we reset the payment
1047        payment.r_amount_approved = 0.0
1048        payment.r_code = u''
1049        payment.p_state = 'unpaid'
1050        payment.r_desc = u''
1051        payment.payment_date = None
1052        self.browser.open(payment_url)
1053        self.browser.getLink("Approve payment").click()
1054        self.assertEqual(payment.p_state, 'paid')
1055        self.assertEqual(payment.r_amount_approved, 200.0)
1056        self.assertEqual(payment.r_code, 'AP')
1057        self.assertTrue(self.applicant.state == 'paid')
1058        # Approval is logged in students.log ...
1059        logfile = os.path.join(
1060            self.app['datacenter'].storage, 'logs', 'applicants.log')
1061        logcontent = open(logfile).read()
1062        self.assertTrue(
1063            'zope.mgr - applicants.browser.OnlinePaymentApprovePage - '
1064            '%s - approved' % self.applicant.applicant_id
1065            in logcontent)
1066        # ... and in payments.log
1067        logfile = os.path.join(
1068            self.app['datacenter'].storage, 'logs', 'payments.log')
1069        logcontent = open(logfile).read()
1070        self.assertTrue(
1071            '"zope.mgr",%s,%s,application,200.0,AP,,,,,,\n'
1072            % (self.applicant.applicant_id, payment.p_id)
1073            in logcontent)
1074        # Payment slips can't be downloaded ...
1075        payment_id = self.applicant.keys()[0]
1076        self.browser.open(self.view_path + '/' + payment_id)
1077        self.browser.getLink("Download payment slip").click()
1078        self.assertTrue(
1079            'Please submit the application form before trying to download payment slips.'
1080            in self.browser.contents)
1081        # ... unless form is submitted.
1082        self.browser.open(self.view_path + '/edit')
1083        image = open(SAMPLE_IMAGE, 'rb')
1084        ctrl = self.browser.getControl(name='form.passport')
1085        file_ctrl = ctrl.mech_control
1086        file_ctrl.add_file(image, filename='myphoto.jpg')
1087        self.browser.getControl(name="confirm_passport").value = True
1088        self.browser.getControl("Finally Submit").click()
1089        self.browser.open(self.view_path + '/' + payment_id)
1090        self.browser.getLink("Download payment slip").click()
1091        self.assertEqual(self.browser.headers['Content-Type'],
1092                 'application/pdf')
1093        return
1094
1095    def prepare_special_container(self):
1096        # Add special application container
1097        container_name = u'special%s' % session_1
1098        applicantscontainer = ApplicantsContainer()
1099        applicantscontainer.code = container_name
1100        applicantscontainer.prefix = 'special'
1101        applicantscontainer.year = session_1
1102        applicantscontainer.title = u'This is a special app container'
1103        applicantscontainer.application_category = 'no'
1104        applicantscontainer.mode = 'create'
1105        applicantscontainer.strict_deadline = True
1106        delta = timedelta(days=10)
1107        applicantscontainer.startdate = datetime.now(pytz.utc) - delta
1108        applicantscontainer.enddate = datetime.now(pytz.utc) + delta
1109        self.app['applicants'][container_name] = applicantscontainer
1110        # Add an applicant
1111        applicant = createObject('waeup.Applicant')
1112        # reg_number is the only field which has to be preset here
1113        # because managers are allowed to edit this required field
1114        applicant.reg_number = u'12345'
1115        self.special_applicant = applicant
1116        self.app['applicants'][container_name].addApplicant(applicant)
1117        IUserAccount(
1118            self.app['applicants'][container_name][
1119            applicant.application_number]).setPassword('apwd')
1120        # Add session configuration object
1121        self.configuration = SessionConfiguration()
1122        self.configuration.academic_session = session_1
1123        #self.configuration.transcript_fee = 200.0
1124        self.configuration.clearance_fee = 300.0
1125        self.app['configuration'].addSessionConfiguration(self.configuration)
1126
1127
1128    def test_pay_special_fee(self):
1129        self.prepare_special_container()
1130        # Login
1131        self.browser.open(self.login_path)
1132        self.browser.getControl(
1133            name="form.login").value = self.special_applicant.applicant_id
1134        self.browser.getControl(name="form.password").value = 'apwd'
1135        self.browser.getControl("Login").click()
1136        applicant_path = self.browser.url
1137        self.browser.getLink("Edit application record").click()
1138        self.browser.getControl(name="form.firstname").value = 'John'
1139        self.browser.getControl(name="form.middlename").value = 'Anthony'
1140        self.browser.getControl(name="form.lastname").value = 'Tester'
1141        self.browser.getControl(name="form.special_application").value = [
1142            'transcript']
1143        self.browser.getControl(name="form.date_of_birth").value = '09/09/1988'
1144        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
1145        self.configuration.transcript_fee = 0.0
1146        self.browser.getControl("Save").click()
1147        self.browser.getControl("Add online payment ticket").click()
1148        self.assertMatches('...Amount could not be determined...',
1149                           self.browser.contents)
1150        self.configuration.transcript_fee = 200.0
1151        self.browser.getLink("Edit application record").click()
1152        self.browser.getControl("Add online payment ticket").click()
1153        self.assertMatches('...Payment ticket created...',
1154                           self.browser.contents)
1155        self.assertTrue(
1156            '<span>Transcript Fee</span>' in self.browser.contents)
1157        self.assertTrue(
1158            'This is a special app container' in self.browser.contents)
1159        self.assertTrue(
1160            '<span>200.0</span>' in self.browser.contents)
1161        self.assertEqual(len(self.special_applicant.keys()), 1)
1162        # The applicant's workflow state is paid ...
1163        self.special_applicant.payments[0].approveApplicantPayment()
1164        self.assertEqual(self.special_applicant.state, 'paid')
1165        self.browser.open(applicant_path + '/edit')
1166        # ... but he can create further tickets.
1167        self.browser.getControl(name="form.special_application").value = [
1168            'clearance']
1169        self.browser.getControl("Save").click()
1170        self.browser.getControl("Add online payment ticket").click()
1171        self.assertMatches('...Payment ticket created...',
1172                           self.browser.contents)
1173        self.browser.open(applicant_path)
1174        self.assertTrue(
1175            '<td>Acceptance Fee</td>' in self.browser.contents)
1176        self.assertEqual(len(self.special_applicant.keys()), 2)
1177        # Second payment can also be approved wthout error message
1178        flashtype, msg, log = self.special_applicant.payments[1].approveApplicantPayment()
1179        self.assertEqual(flashtype, 'success')
1180        self.assertEqual(msg, 'Payment approved')
1181        # Payment slips can't be downloaded ...
1182        payment_id = self.special_applicant.keys()[0]
1183        self.browser.open(applicant_path + '/' + payment_id)
1184        self.browser.getLink("Download payment slip").click()
1185        self.assertTrue(
1186            'Please submit the application form before trying to download payment slips.'
1187            in self.browser.contents)
1188        # ... unless form is submitted.
1189        self.browser.open(applicant_path + '/edit')
1190        image = open(SAMPLE_IMAGE, 'rb')
1191        ctrl = self.browser.getControl(name='form.passport')
1192        file_ctrl = ctrl.mech_control
1193        file_ctrl.add_file(image, filename='myphoto.jpg')
1194        self.browser.getControl(name="confirm_passport").value = True
1195        self.browser.getControl("Finally Submit").click()
1196        self.browser.open(applicant_path + '/' + payment_id)
1197        self.browser.getLink("Download payment slip").click()
1198        self.assertEqual(self.browser.headers['Content-Type'],
1199                 'application/pdf')
1200        return
1201
1202    def test_final_submit(self):
1203        # Make sure that a correctly filled form with passport picture
1204        # can be submitted (only) after payment
1205        self.login()
1206        self.browser.getLink("Edit application record").click()
1207        self.assertFalse('Finally Submit' in self.browser.contents)
1208        IWorkflowInfo(self.applicant).fireTransition('pay')
1209        self.browser.open(self.edit_path)
1210        self.assertTrue('Finally Submit' in self.browser.contents)
1211        self.fill_correct_values() # fill other fields with correct values
1212        self.browser.getControl("Save").click()
1213        self.browser.getControl("Finally Submit").click()
1214        # We forgot to upload a passport picture
1215        self.assertTrue(
1216            'No passport picture uploaded' in self.browser.contents)
1217        # Use a real image file and select it to be uploaded in form
1218        image = open(SAMPLE_IMAGE, 'rb')
1219        ctrl = self.browser.getControl(name='form.passport')
1220        file_ctrl = ctrl.mech_control
1221        file_ctrl.add_file(image, filename='myphoto.jpg')
1222        self.browser.getControl("Finally Submit").click() # (finally) submit form
1223        # The picture has been uploaded but the form cannot be submitted
1224        # since the passport confirmation box was not ticked
1225        self.assertTrue(
1226            'Passport picture confirmation box not ticked'
1227            in self.browser.contents)
1228        self.browser.getControl(name="confirm_passport").value = True
1229        # If application period has expired and strict-deadline is set
1230        # applicants do notsee edit button and can't open
1231        # the edit form.
1232        self.applicantscontainer.enddate = datetime.now(pytz.utc)
1233        self.browser.open(self.view_path)
1234        self.assertFalse(
1235            'Edit application record' in self.browser.contents)
1236        self.browser.open(self.edit_path)
1237        self.assertTrue(
1238            'form is locked' in self.browser.contents)
1239        # We can either postpone the enddate ...
1240        self.applicantscontainer.enddate = datetime.now(
1241            pytz.utc) + timedelta(days=10)
1242        self.browser.open(self.edit_path)
1243        self.browser.getControl(name="confirm_passport").value = True
1244        self.browser.getControl("Finally Submit").click()
1245        self.assertTrue(
1246            'Application submitted' in self.browser.contents)
1247        # ... or allow submission after deadline.
1248        IWorkflowState(self.applicant).setState('paid')
1249        self.applicant.locked = False
1250        self.applicantscontainer.strict_deadline = False
1251        self.browser.open(self.edit_path)
1252        self.browser.getControl(name="confirm_passport").value = True
1253        self.browser.getControl("Finally Submit").click()
1254        self.assertTrue(
1255            'Application submitted' in self.browser.contents)
1256        return
1257
1258    def test_locking(self):
1259        # Make sure that locked forms can't be submitted
1260        self.login()
1261        self.browser.open(self.edit_path)
1262        self.fill_correct_values() # fill other fields with correct values
1263        # Create a pseudo image file and select it to be uploaded in form
1264        pseudo_image = StringIO('I pretend to be a graphics file')
1265        ctrl = self.browser.getControl(name='form.passport')
1266        file_ctrl = ctrl.mech_control
1267        file_ctrl.add_file(pseudo_image, filename='myphoto.jpg')
1268        self.browser.getControl("Save").click()
1269        # Now we lock the form
1270        self.applicant.locked = True
1271        self.browser.open(self.edit_path)
1272        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1273        self.assertTrue(
1274            'The requested form is locked' in self.browser.contents)
1275        return
1276
1277    def test_certificate_removed(self):
1278        self.login()
1279        self.browser.open(self.edit_path)
1280        self.fill_correct_values()
1281        self.browser.getControl("Save").click()
1282        self.browser.open(self.view_path)
1283        self.assertTrue(
1284            'Unnamed Certificate' in self.browser.contents)
1285        self.browser.open(self.edit_path)
1286        self.assertTrue(
1287            '<option selected="selected" value="CERT1">' in self.browser.contents)
1288        # Now we remove the certificate
1289        del self.app['faculties']['fac1']['dep1'].certificates['CERT1']
1290        # The certificate is still shown in display mode
1291        self.browser.open(self.view_path)
1292        self.assertTrue(
1293            'Unnamed Certificate' in self.browser.contents)
1294        # The certificate is still selectable in edit mode so that it won't
1295        # be automatically replaced by another (arbitrary) certificate
1296        self.browser.open(self.edit_path)
1297        self.assertTrue(
1298            '<option selected="selected" value="CERT1">' in self.browser.contents)
1299        # Consequently, the certificate is still shown after saving the form
1300        self.browser.getControl("Save").click()
1301        self.browser.open(self.view_path)
1302        self.assertTrue(
1303            'Unnamed Certificate' in self.browser.contents)
1304        # Even if we add a new certificate the previous (removed)
1305        # certificate is shown
1306        certificate = createObject('waeup.Certificate')
1307        certificate.code = 'CERT2'
1308        certificate.title = 'New Certificate'
1309        certificate.application_category = 'basic'
1310        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
1311            certificate)
1312        self.browser.open(self.edit_path)
1313        self.assertTrue(
1314            '<option selected="selected" value="CERT1">'
1315            in self.browser.contents)
1316
1317    def test_upload_testfile(self):
1318        self.login()
1319        self.browser.open(self.edit_path)
1320        self.fill_correct_values() # fill other fields with correct values       
1321        # Create a pseudo file with acceptable size
1322        pdf_content = 'A' * 1024 * 300  # A string of 300 KB size
1323        pseudo_pdf = StringIO(pdf_content)
1324        ctrl = self.browser.getControl(name='testfile.pdf')
1325        file_ctrl = ctrl.mech_control
1326        file_ctrl.add_file(pseudo_pdf, filename='testfile.pdf')
1327        self.browser.getControl("Save").click() # submit form
1328        self.assertTrue('Uploaded file is too big!'
1329            in self.browser.contents)
1330        pdf_content = 'A' * 1024 * 200  # A string of 300 KB size
1331        pseudo_pdf = StringIO(pdf_content)
1332        ctrl = self.browser.getControl(name='testfile.pdf')
1333        file_ctrl = ctrl.mech_control
1334        file_ctrl.add_file(pseudo_pdf, filename='testfile.pdf')
1335        self.browser.getControl("Save").click() # submit form
1336        # The file has been successfully uploaded
1337        self.assertTrue('Form has been saved.' in self.browser.contents)
1338        # There is really a file stored for the applicant
1339        storage = getUtility(IExtFileStore)
1340        file_id = IFileStoreNameChooser(self.applicant).chooseName(
1341            attr='testfile.pdf')
1342        # The stored file can be fetched
1343        fd = storage.getFile(file_id)
1344        file_len = len(fd.read())
1345        self.assertEqual(file_len, 204800)
1346        # A file link is displayed on the edit view ...
1347        self.browser.open(self.edit_path)
1348        self.assertTrue('<a href="testfile.pdf">' in self.browser.contents)
1349        # ... and on the dislay view
1350        self.browser.open(self.view_path)
1351        self.assertTrue('testfile.pdf">Test File</a>'
1352            in self.browser.contents)
1353        # Adding file is properly logged
1354        logfile = os.path.join(
1355            self.app['datacenter'].storage, 'logs', 'applicants.log')
1356        logcontent = open(logfile).read()
1357        self.assertTrue(
1358            '%s - applicants.browser.ApplicantEditFormPage'
1359            ' - %s - saved: testfile.pdf'
1360            % (self.applicant.applicant_id, self.applicant.applicant_id)
1361            in logcontent)
1362        # When an applicant is removed, also the pdf files are gone.
1363        del self.applicantscontainer[self.applicant.application_number]
1364        fd = storage.getFile(file_id)
1365        self.assertTrue(fd is None)
1366        return
1367
1368    def test_manage_contact_applicant(self):
1369        # Managers can contact student
1370        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1371        # Remove required FieldProperty attribute first ...
1372        delattr(Applicant, 'email')
1373        # ... and replace by None
1374        self.applicant.email = None
1375        # Now we have to add  the FieldProperty attribute again. Otherwise
1376        # many other tests below will fail.
1377        iface = list(implementedBy(Applicant))[0]
1378        field_property = FieldProperty(iface['email'])
1379        setattr(Applicant, 'email', field_property)
1380        self.browser.open(self.view_path)
1381        self.browser.getLink("Send email").click()
1382        self.browser.getControl(
1383            name="form.subject").value = 'Important subject'
1384        self.browser.getControl(name="form.body").value = 'Hello!'
1385        self.browser.getControl("Send message now").click()
1386        self.assertTrue(
1387            'An smtp server error occurred' in self.browser.contents)
1388        self.applicant.email = 'xx@yy.zz'
1389        self.browser.getControl("Send message now").click()
1390        self.assertTrue('Your message has been sent' in self.browser.contents)
1391        self.assertMatches(
1392            'Sending email from no-reply@waeup.org to xx@yy.zz:'
1393            '\nMessage:'
1394            '\nmsg: MIME-Version: 1.0'
1395            '\nmsg: Content-Type: text/plain; charset="us-ascii"'
1396            '\nmsg: Content-Transfer-Encoding: 7bit'
1397            '\nmsg: From: Manager <no-reply@waeup.org>'
1398            '\nmsg: To: Joan None <xx@yy.zz>'
1399            '\nmsg: Reply-To: Manager <contact@waeup.org>'
1400            '\nmsg: Subject: Important subject'
1401            '\nmsg:'
1402            '\nmsg: Hello!'
1403            '\nmsg:'
1404            '\nmsg: ---'
1405            '\nmsg: Manager (id: zope.mgr)'
1406            '\nmsg: Sample University'
1407            '\nmsg:',
1408            self.get_fake_smtp_output()
1409            )
1410        return
1411
1412
1413class ApplicantRegisterTests(ApplicantsFullSetup):
1414    # Tests for applicant registration
1415
1416    layer = FunctionalLayer
1417
1418    def test_register_applicant_create(self):
1419        config = grok.getSite()['configuration']
1420        config.maintmode_enabled_by = u'any_user'
1421        self.assertEqual(len(self.app['applicants'][container_name_1]), 1)
1422        # An applicant can register himself.
1423        self.browser.open(self.container_path)
1424        self.browser.getLink("Register for application").click()
1425        self.assertTrue(
1426            'The portal is in maintenance mode' in self.browser.contents)
1427        config.maintmode_enabled_by = None
1428        self.browser.getLink("Register for application").click()
1429        # The edit form now opens and can be filled with suitable values
1430        self.browser.getControl(name="form.firstname").value = 'Anna'
1431        self.browser.getControl(name="form.lastname").value = 'Kurios'
1432        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
1433        self.browser.getControl(name="form.phone.country").value = ['+234']
1434        self.browser.getControl(name="form.phone.area").value = '555'
1435        self.browser.getControl(name="form.phone.ext").value = '6666666'
1436        self.browser.getControl("Send login credentials").click()
1437        self.assertEqual(self.browser.url,
1438            self.container_path + '/registration_complete?email=xx%40yy.zz')
1439        # A new applicant has been created
1440        self.assertEqual(len(self.app['applicants'][container_name_1]), 2)
1441        # The new applicant can be found in the catalog via the email address
1442        cat = getUtility(ICatalog, name='applicants_catalog')
1443        results = list(
1444            cat.searchResults(email=('xx@yy.zz', 'xx@yy.zz')))
1445        applicant = results[0]
1446        self.assertEqual(applicant.lastname,'Kurios')
1447        # The application_id has been copied to the reg_number
1448        #self.assertEqual(applicant.applicant_id, applicant.reg_number)
1449        # The applicant can be found in the catalog via the reg_number
1450        #results = list(
1451        #    cat.searchResults(
1452        #    reg_number=(applicant.reg_number, applicant.reg_number)))
1453        #self.assertEqual(applicant,results[0])
1454        return
1455
1456    def test_register_applicant_take_unused_record(self):
1457        # Create an unused record
1458        uu_applicant = createObject('waeup.Applicant')
1459        self.app['applicants'][container_name_1].addApplicant(uu_applicant)
1460        self.assertEqual(uu_applicant.container_code, container_name_1 + '-')
1461        self.assertEqual(len(self.app['applicants'][container_name_1]), 2)
1462        self.browser.open(self.container_path)
1463        self.browser.getLink("Register for application").click()
1464        # Fill the edit form with suitable values
1465        self.browser.getControl(name="form.firstname").value = 'Anna'
1466        self.browser.getControl(name="form.lastname").value = 'Kurios'
1467        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
1468        self.browser.getControl(name="form.phone.country").value = ['+234']
1469        self.browser.getControl(name="form.phone.area").value = '555'
1470        self.browser.getControl(name="form.phone.ext").value = '6666666'
1471        self.browser.getControl("Send login credentials").click()
1472        # No applicant has been created ...
1473        self.assertEqual(len(self.app['applicants'][container_name_1]), 2)
1474        # ... and the existing, formerly unused record has been used instead
1475        self.assertEqual(uu_applicant.lastname, 'Kurios')
1476        self.assertEqual(uu_applicant.container_code, container_name_1 + '+')
1477        return
1478
1479    def test_register_applicant_update(self):
1480        # We change the application mode and check if applicants
1481        # can find and update imported records instead of creating new records.
1482        # First we check what happens if record does not exist.
1483        self.applicantscontainer.mode = 'update'
1484        self.browser.open(self.container_path + '/register')
1485        self.browser.getControl(name="form.lastname").value = 'Better'
1486        self.browser.getControl(name="form.reg_number").value = 'anynumber'
1487        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
1488        self.browser.getControl("Send login credentials").click()
1489        self.assertTrue('No application record found.'
1490            in self.browser.contents)
1491        # Even with the correct reg_number we can't register
1492        # because lastname attribute is not set.
1493        self.applicantscontainer.mode = 'update'
1494        self.browser.open(self.container_path + '/register')
1495        self.browser.getControl(name="form.lastname").value = 'Better'
1496        self.browser.getControl(name="form.reg_number").value = '1234'
1497        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
1498        self.browser.getControl("Send login credentials").click()
1499        self.assertTrue('An error occurred.' in self.browser.contents)
1500        # Let's set this attribute manually
1501        # and try to register with a wrong name.
1502        self.applicant.lastname = u'Better'
1503        self.browser.open(self.container_path + '/register')
1504        self.browser.getControl(name="form.lastname").value = 'Worse'
1505        self.browser.getControl(name="form.reg_number").value = '1234'
1506        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
1507        self.browser.getControl("Send login credentials").click()
1508        # Anonymous is not informed that lastname verification failed.
1509        # It seems that the record doesn't exist.
1510        self.assertTrue('No application record found.'
1511            in self.browser.contents)
1512        # Even with the correct lastname we can't register if a
1513        # password has been set and used.
1514        IWorkflowState(self.applicant).setState('started')
1515        self.browser.getControl(name="form.lastname").value = 'Better'
1516        self.browser.getControl(name="form.reg_number").value = '1234'
1517        self.browser.getControl("Send login credentials").click()
1518        self.assertTrue('Your password has already been set and used.'
1519            in self.browser.contents)
1520        #IUserAccount(
1521        #    self.app['applicants'][container_name_1][
1522        #    self.applicant.application_number]).context.password = None
1523        # Even without unsetting the password we can re-register if state
1524        # is 'initialized'
1525        IWorkflowState(self.applicant).setState('initialized')
1526        self.browser.open(self.container_path + '/register')
1527        # The lastname field, used for verification, is not case-sensitive.
1528        self.browser.getControl(name="form.lastname").value = 'bEtter'
1529        self.browser.getControl(name="form.reg_number").value = '1234'
1530        self.browser.getControl(name="form.email").value = 'new@yy.zz'
1531        self.browser.getControl("Send login credentials").click()
1532        # Yeah, we succeded ...
1533        self.assertTrue('Your registration was successful.'
1534            in self.browser.contents)
1535        # ... and  applicant can be found in the catalog via the email address
1536        cat = getUtility(ICatalog, name='applicants_catalog')
1537        results = list(
1538            cat.searchResults(
1539            email=('new@yy.zz', 'new@yy.zz')))
1540        self.assertEqual(self.applicant,results[0])
1541        return
1542
1543    def test_change_password_request(self):
1544        self.browser.open('http://localhost/app/changepw')
1545        self.browser.getControl(name="form.identifier").value = '1234'
1546        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
1547        self.browser.getControl("Send login credentials").click()
1548        self.assertTrue('No record found' in self.browser.contents)
1549        self.applicant.email = 'aa@aa.ng'
1550        # Update the catalog
1551        notify(grok.ObjectModifiedEvent(self.applicant))
1552        self.browser.open('http://localhost/app/changepw')
1553        self.browser.getControl(name="form.identifier").value = '1234'
1554        self.browser.getControl(name="form.email").value = 'aa@aa.ng'
1555        self.browser.getControl("Send login credentials").click()
1556        self.assertTrue(
1557            'An email with your user name and password has been sent'
1558            in self.browser.contents)
1559
1560    def test_check_status(self):
1561        self.applicant.lastname = u'Lion '
1562        self.browser.open('http://localhost/app/applicants/checkstatus')
1563        self.browser.getControl(name="unique_id").value = 'nonsense'
1564        self.browser.getControl(name="lastname").value = 'Lion'
1565        self.browser.getControl("Submit").click()
1566        self.assertTrue('No application record found' in self.browser.contents)
1567        self.browser.getControl(
1568          name="unique_id").value = self.applicant.applicant_id
1569        self.browser.getControl(name="lastname").value = 'nonsense'
1570        self.browser.getControl("Submit").click()
1571        self.assertTrue('No application record found' in self.browser.contents)
1572        self.browser.getControl(
1573          name="unique_id").value = self.applicant.applicant_id
1574        self.browser.getControl(name="lastname").value = 'Lion'
1575        self.browser.getControl("Submit").click()
1576        self.assertTrue('Admission status of' in self.browser.contents)
1577        self.assertTrue(
1578          'You have not yet submitted your application' in self.browser.contents)
1579        IWorkflowState(self.applicant).setState('admitted')
1580        self.browser.open('http://localhost/app/applicants/checkstatus')
1581        self.browser.getControl(
1582          name="unique_id").value = self.applicant.applicant_id
1583        # Whitespaces are ignored.
1584        self.browser.getControl(name="lastname").value = 'Lion'
1585        self.browser.getControl("Submit").click()
1586        self.assertTrue('Congratulations!' in self.browser.contents)
1587        self.assertFalse('Study Course' in self.browser.contents)
1588        self.applicant.course_admitted = self.certificate
1589        self.browser.open('http://localhost/app/applicants/checkstatus')
1590        self.browser.getControl(
1591          name="unique_id").value = self.applicant.applicant_id
1592        self.browser.getControl(name="lastname").value = 'Lion'
1593        self.browser.getControl("Submit").click()
1594        self.assertTrue('Congratulations!' in self.browser.contents)
1595        self.assertTrue('Unnamed Certificate (CERT1)' in self.browser.contents)
1596        self.assertTrue(
1597          'Department of Unnamed Department (dep1)' in self.browser.contents)
1598        self.assertTrue(
1599          'Faculty of Unnamed Faculty (NA)' in self.browser.contents)
1600        # Also the reg_number can be used and page shows student id and password
1601        # if applicant is in state created.
1602        IWorkflowState(self.applicant).setState('created')
1603        self.applicant.student_id = u'my id'
1604        self.browser.open('http://localhost/app/applicants/checkstatus')
1605        self.browser.getControl(
1606          name="unique_id").value = self.applicant.reg_number
1607        self.browser.getControl(name="lastname").value = 'Lion'
1608        self.browser.getControl("Submit").click()
1609        self.assertTrue('Congratulations!' in self.browser.contents)
1610        self.assertTrue('Unnamed Certificate (CERT1)' in self.browser.contents)
1611        self.assertTrue('Department of Unnamed Department (dep1)'
1612            in self.browser.contents)
1613        self.assertTrue(
1614          'Faculty of Unnamed Faculty (NA)' in self.browser.contents)
1615        self.assertTrue('user name (= student id) is: <strong>my id</strong>'
1616            in self.browser.contents)
1617        self.assertTrue(
1618          'password is: <strong>%s</strong>' % self.applicant.application_number
1619          in self.browser.contents)
1620
1621    def test_check_transcript_status(self):
1622        self.applicant.email = 'aa@aa.aa'
1623        self.browser.open('http://localhost/app/applicants/checktranscript')
1624        self.browser.getControl(name="unique_id").value = 'nonsense'
1625        self.browser.getControl(name="email").value = 'aa@aa.aa'
1626        self.browser.getControl("Check status now").click()
1627        self.assertTrue('No student record was found in Kofa'
1628            in self.browser.contents)
1629
1630class ApplicantsExportTests(ApplicantsFullSetup, FunctionalAsyncTestCase):
1631    # Tests for StudentsContainer class views and pages
1632
1633    layer = FunctionalLayer
1634
1635    def wait_for_export_jobs_completed(self):
1636        # helper function waiting until the current export job is completed
1637        manager = getUtility(IJobManager)
1638        job_ids = [i[0] for i in self.app['datacenter'].running_exports]
1639        jobs = [manager.get(job_id) for job_id in job_ids]
1640        for job in jobs:
1641            wait_for_result(job)
1642        return job_ids
1643
1644    def test_applicants_in_container_export(self):
1645        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1646        container_path = 'http://localhost/app/applicants/%s' % container_name_1
1647        self.browser.open(container_path)
1648        self.browser.getLink("Export application data").click()
1649        self.browser.getControl("Start new exports").click()
1650        job_ids = self.wait_for_export_jobs_completed()
1651        # Three exports were created
1652        self.assertEqual(len(self.app['datacenter'].running_exports), 3)
1653       # When the jobs are finished and we reload the page...
1654        self.browser.open(container_path + '/exports')
1655        # ... the both csv files can be downloaded ...
1656        self.browser.getLink("Download", index=0).click()
1657        self.assertEqual(self.browser.headers['content-type'],
1658            'text/csv; charset=UTF-8')
1659        self.assertTrue(
1660            'filename="WAeUP.Kofa_applicants_%s.csv' % job_ids[0] in
1661            self.browser.headers['content-disposition'])
1662        self.browser.open(container_path + '/exports')
1663        self.browser.getLink("Download", index=1).click()
1664        self.assertEqual(self.browser.headers['content-type'],
1665            'text/csv; charset=UTF-8')
1666        self.assertTrue(
1667            'filename="WAeUP.Kofa_applicantpayments_%s.csv' % job_ids[1] in
1668            self.browser.headers['content-disposition'])
1669        self.browser.open(container_path + '/exports')
1670        self.browser.getLink("Download", index=2).click()
1671        self.assertEqual(self.browser.headers['content-type'],
1672            'text/csv; charset=UTF-8')
1673        self.assertTrue(
1674            'filename="WAeUP.Kofa_applicantrefereereports_%s.csv' % job_ids[2] in
1675            self.browser.headers['content-disposition'])
1676        # ... and discarded
1677        self.browser.open(container_path + '/exports')
1678        self.browser.getControl("Discard", index=0).click()
1679        self.assertEqual(len(self.app['datacenter'].running_exports), 2)
1680        self.browser.getControl("Discard", index=0).click()
1681        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
1682        self.browser.getControl("Discard").click()
1683        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
1684        # Creation, downloading and discarding are logged
1685        logfile = os.path.join(
1686            self.app['datacenter'].storage, 'logs', 'datacenter.log')
1687        logcontent = open(logfile).read()
1688        self.assertTrue(
1689            'zope.mgr - applicants.browser.ExportJobContainerJobStart - '
1690            'exported: applicants (%s), job_id=%s'
1691            % (container_name_1, job_ids[0]) in logcontent
1692            )
1693        self.assertTrue(
1694            'zope.mgr - applicants.browser.ExportJobContainerDownload '
1695            '- downloaded: WAeUP.Kofa_applicants_%s.csv, job_id=%s'
1696            % (job_ids[0], job_ids[0]) in logcontent
1697            )
1698        self.assertTrue(
1699            'zope.mgr - applicants.browser.ExportJobContainerOverview '
1700            '- discarded: job_id=%s' % job_ids[0] in logcontent
1701            )
1702        self.assertTrue(
1703            'zope.mgr - applicants.browser.ExportJobContainerJobStart - '
1704            'exported: applicantpayments (%s), job_id=%s'
1705            % (container_name_1, job_ids[1]) in logcontent
1706            )
1707        self.assertTrue(
1708            'zope.mgr - applicants.browser.ExportJobContainerDownload '
1709            '- downloaded: WAeUP.Kofa_applicantpayments_%s.csv, job_id=%s'
1710            % (job_ids[1], job_ids[1]) in logcontent
1711            )
1712        self.assertTrue(
1713            'zope.mgr - applicants.browser.ExportJobContainerOverview '
1714            '- discarded: job_id=%s' % job_ids[1] in logcontent
1715            )
1716        self.assertTrue(
1717            'zope.mgr - applicants.browser.ExportJobContainerJobStart - '
1718            'exported: applicantrefereereports (%s), job_id=%s'
1719            % (container_name_1, job_ids[2]) in logcontent
1720            )
1721        self.assertTrue(
1722            'zope.mgr - applicants.browser.ExportJobContainerDownload '
1723            '- downloaded: WAeUP.Kofa_applicantrefereereports_%s.csv, job_id=%s'
1724            % (job_ids[2], job_ids[2]) in logcontent
1725            )
1726        self.assertTrue(
1727            'zope.mgr - applicants.browser.ExportJobContainerOverview '
1728            '- discarded: job_id=%s' % job_ids[2] in logcontent
1729            )
1730
1731class ApplicantRefereeReportTests(ApplicantsFullSetup, FunctionalAsyncTestCase):
1732    # Tests for ApplicantRefereeReport class views and pages
1733
1734    layer = FunctionalLayer
1735
1736    def setUp(self):
1737        super(ApplicantRefereeReportTests, self).setUp()
1738        self.setup_logging()
1739        return
1740
1741    def tearDown(self):
1742        super(ApplicantRefereeReportTests, self).tearDown()
1743        self.teardown_logging()
1744        return
1745
1746    def setup_logging(self):
1747        # setup a log-handler that catches all fake mailer output
1748        self.stream = StringIO()
1749        handler = logging.StreamHandler(self.stream)
1750        logger = logging.getLogger('test.smtp')
1751        logger.addHandler(handler)
1752        logger.setLevel(logging.INFO)
1753        return
1754
1755    def get_fake_smtp_output(self):
1756        # get output generated by fake mailer
1757        self.stream.flush()
1758        self.stream.seek(0)
1759        return self.stream.read()
1760
1761    def teardown_logging(self):
1762        # remove the log handler for fake mailer output
1763        logger = logging.getLogger('test.smtp')
1764        handlers = [x for x in logger.handlers]
1765        for handler in handlers:
1766            logger.removeHandler(handler)
1767        return
1768
1769    def test_refereereport_mandate(self):
1770        mandate = RefereeReportMandate()
1771        mandate.params['name'] = u'John Referee'
1772        mandate.params['email'] = 'aa@aa.aa'
1773        mandate.params['applicant_id'] = self.applicant.applicant_id
1774        mandate.params[
1775            'redirect_path'] = '/applicants/%s/%s/addrefereereport' % (
1776                container_name_1, self.applicant.application_number)
1777        self.app['mandates'].addMandate(mandate)
1778        # Let's open the add form page via the mandate view
1779        self.browser.open('http://localhost/app/mandate?mandate_id=%s'
1780            % mandate.mandate_id)
1781        # Form page opens and is prefilled
1782        self.assertEqual(
1783            self.browser.url,
1784            'http://localhost/app/applicants/%s/%s/addrefereereport?mandate_id=%s'
1785            % (container_name_1, self.applicant.application_number,
1786            mandate.mandate_id))
1787        self.assertTrue('value="John Referee"' in self.browser.contents)
1788        # Let's open the page directly with an invalid mandate
1789        self.browser.open(
1790            'http://localhost/app/applicants/%s/%s/addrefereereport?mandate_id=wrongmadate'
1791            % (container_name_1, self.applicant.application_number))
1792        self.assertTrue('<div class="alert alert-warning">No mandate.</div>'
1793            in self.browser.contents)
1794        self.assertEqual(self.browser.url, 'http://localhost/app')
1795        # Page is also blocked in maintenance mode
1796        grok.getSite()['configuration'].maintmode_enabled_by = u'anybody'
1797        self.browser.open(
1798            'http://localhost/app/applicants/%s/%s/addrefereereport?mandate_id=%s'
1799            % (container_name_1, self.applicant.application_number,
1800            mandate.mandate_id))
1801        self.assertTrue('<div class="alert alert-warning">The portal is '
1802                        'in maintenance mode'
1803            in self.browser.contents)
1804        self.assertEqual(self.browser.url, 'http://localhost/app')
1805        return
1806
1807    def test_add_and_view_manage_reports(self):
1808        mandate = RefereeReportMandate()
1809        mandate.params['name'] = u'John Referee'
1810        mandate.params['email'] = 'aa@aa.aa'
1811        mandate.params['applicant_id'] = self.applicant.applicant_id
1812        mandate.params['redirect_path'] = '/applicants/%s/%s/addrefereereport' % (
1813                container_name_1, self.applicant.application_number)
1814        mandate.params['redirect_path2'] = ''
1815        self.app['mandates'].addMandate(mandate)
1816        self.assertEqual(len(self.app['mandates'].keys()), 1)
1817        # Let's open the add form page via the mandate view
1818        self.browser.open('http://localhost/app/mandate?mandate_id=%s'
1819            % mandate.mandate_id)
1820        self.assertTrue('Joan None' in self.browser.contents)
1821        self.assertTrue('John Referee' in self.browser.contents)
1822        # Report can't be saved without required fields
1823        self.browser.getControl(name="form.name").value = ''
1824        self.browser.getControl("Submit").click()
1825        self.assertTrue('Required input is missing' in self.browser.contents)
1826        self.browser.getControl(name="form.name").value = 'Johnny Referee'
1827        self.browser.getControl("Submit").click()
1828        # Referee will be redirected to the frontpage
1829        self.assertEqual(self.browser.url, 'http://localhost/app')
1830        self.assertTrue('Your report has been successfully submitted. '
1831                        'Please use the report link in the email again '
1832                        'to download a pdf slip of your report.'
1833            in self.browser.contents)
1834        # If they use the mandate again, they will be redirected to a pdf file
1835        self.browser.open('http://localhost/app/mandate?mandate_id=%s'
1836            % mandate.mandate_id)
1837        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1838        self.assertEqual(self.browser.headers['Content-Type'],
1839                         'application/pdf')
1840        path = os.path.join(samples_dir(), 'referee_report.pdf')
1841        open(path, 'wb').write(self.browser.contents)
1842        print "Sample PDF referee_report.pdf written to %s" % path
1843        # Report has been created
1844        self.assertEqual(len(self.applicant.refereereports), 1)
1845        report = self.applicant.refereereports[0]
1846        # The email address has been stored
1847        self.assertEqual(report.email, 'aa@aa.aa')
1848        # Referee can use mandate again to download the pdf report
1849        self.browser.open('http://localhost/app/mandate?mandate_id=%s'
1850            % mandate.mandate_id)
1851        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1852        self.assertEqual(self.browser.headers['Content-Type'],
1853                         'application/pdf')
1854        # Referees can't use another mandate for adding a new report
1855        mandate2 = RefereeReportMandate()
1856        mandate2.params['name'] = u'John Referee'
1857        mandate2.params['email'] = 'aa@aa.aa'
1858        mandate2.params['applicant_id'] = self.applicant.applicant_id
1859        mandate2.params['redirect_path'] = '/applicants/%s/%s/addrefereereport' % (
1860                container_name_1, self.applicant.application_number)
1861        mandate2.params['redirect_path2'] = ''
1862        self.app['mandates'].addMandate(mandate2)
1863        self.browser.open('http://localhost/app/mandate?mandate_id=%s'
1864            % mandate2.mandate_id)
1865        self.assertTrue('You have already created a report with another mandate'
1866            in self.browser.contents)
1867        # Managers can view the report
1868        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
1869        self.browser.open(self.manage_path)
1870        self.browser.getLink("%s" % report.r_id).click()
1871        self.assertEqual(
1872            self.browser.url, self.view_path  + '/%s' % report.r_id)
1873        self.assertTrue('Johnny Referee' in self.browser.contents)
1874        # Managers can download a pdf slip
1875        self.browser.getLink("Download referee report").click()
1876        self.assertEqual(self.browser.headers['Status'], '200 Ok')
1877        self.assertEqual(self.browser.headers['Content-Type'],
1878                         'application/pdf')
1879        # Mandate is not deleted ...
1880        self.assertEqual(len(self.app['mandates'].keys()), 2)
1881        # ... but redirect_path2 attribute has been set
1882        redirect_path2 = '/applicants/%s/%s/%s/referee_report.pdf' % (
1883                container_name_1,
1884                self.applicant.application_number,
1885                report.r_id)
1886        self.assertEqual(
1887            self.app['mandates'][mandate.mandate_id].params['redirect_path2'],
1888            redirect_path2)
1889        # Portal managers can edit referee reports (no button available!)
1890        self.browser.open(self.view_path + '/%s/manage' % report.r_id)
1891        self.browser.getControl(name="form.email_pref").value = 'bb@bb.de'
1892        self.browser.getControl("Save").click()
1893        self.assertEqual(report.email_pref, 'bb@bb.de')
1894        # Managers can delete referee reports
1895        self.browser.open(self.manage_path)
1896        self.browser.getLink("%s" % report.r_id).click()
1897        self.assertEqual(len(self.applicant.refereereports), 1)
1898        self.browser.getLink("Delete").click()
1899        self.assertEqual(len(self.applicant.refereereports), 0)
1900        self.assertTrue('Referee report removed.' in self.browser.contents)
1901        self.assertEqual(self.browser.url, self.view_path)
1902        # Report creation, managing and deletion is logged
1903        logfile = os.path.join(
1904            self.app['datacenter'].storage, 'logs', 'applicants.log')
1905        logcontent = open(logfile).read()
1906        self.assertTrue(
1907            'zope.anybody - applicants.browser.RefereeReportAddFormPage - '
1908            '%s - added: %s\n' % (self.applicant.applicant_id, report.r_id)
1909            in logcontent)
1910        self.assertTrue(
1911            'zope.mgr - applicants.browser.RefereeReportManageFormPage - '
1912            '%s - %s - saved: email_pref\n' % (
1913            self.applicant.applicant_id, report.r_id) in logcontent)
1914        self.assertTrue(
1915            'zope.mgr - applicants.browser.RemoveRefereeReportPage - '
1916            '%s - removed: %s\n' % (self.applicant.applicant_id, report.r_id)
1917            in logcontent)
1918        return
1919
1920    def test_final_submit_with_referees(self):
1921        # Add two referees
1922        referee1 = RefereeEntry()
1923        referee2 = RefereeEntry()
1924        referee1.name = u'Linda Tree'
1925        referee1.email = 'linda@forest.de'
1926        referee2.name = u'Otis Stone'
1927        referee2.email = 'otis@stones.de'
1928        self.applicant.referees = [referee1, referee2]
1929        self.assertFalse(referee1.email_sent)
1930        self.assertFalse(referee2.email_sent)
1931        self.login()
1932        IWorkflowInfo(self.applicant).fireTransition('pay')
1933        self.browser.open(self.edit_path)
1934        self.fill_correct_values() # fill other fields with correct values
1935        image = open(SAMPLE_IMAGE, 'rb')
1936        ctrl = self.browser.getControl(name='form.passport')
1937        file_ctrl = ctrl.mech_control
1938        file_ctrl.add_file(image, filename='myphoto.jpg')
1939        self.browser.getControl("Save").click()
1940        self.browser.getControl(name="confirm_passport").value = True
1941        self.browser.getControl("Finally Submit").click()
1942        if self.app['mandates'].values()[0].params['name'] == 'Linda Tree':
1943            mandate_id_0 = self.app['mandates'].keys()[0]
1944            mandate_id_1 = self.app['mandates'].keys()[1]
1945        else:
1946            mandate_id_0 = self.app['mandates'].keys()[1]
1947            mandate_id_1 = self.app['mandates'].keys()[0]
1948        self.assertMatches(
1949            'Sending email from no-reply@waeup.org to linda@forest.de:'
1950            '\nMessage:'
1951            '\nmsg: MIME-Version: 1.0\nmsg: Content-Type: text/plain; charset="us-ascii"'
1952            '\nmsg: Content-Transfer-Encoding: 7bit'
1953            '\nmsg: From: Administrator <no-reply@waeup.org>'
1954            '\nmsg: To: Linda Tree <linda@forest.de>'
1955            '\nmsg: Reply-To: Administrator <contact@waeup.org>'
1956            '\nmsg: Subject: Request for referee report from Sample University'
1957            '\nmsg: '
1958            '\nmsg: Dear Linda Tree,'
1959            '\nmsg: '
1960            '\nmsg: The candidate with Id app%s_372052 and name John Anthony Tester applied to'
1961            '\nmsg: the Sample University to study Unnamed Certificate for the %s/%s session.'
1962            '\nmsg: The candidate has listed you as referee. You are thus required to kindly use'
1963            '\nmsg: the link below to provide your referral remarks on or before'
1964            '\nmsg: 2016-08-12 08:32:41.619671+00:00.'
1965            '\nmsg: '
1966            '\nmsg: Report link: http://localhost/app/mandate?mandate_id=%s'
1967            '\nmsg: '
1968            '\nmsg: Thank You'
1969            '\nmsg: '
1970            '\nmsg: The Secretary'
1971            '\nmsg: Post Graduate School'
1972            '\nmsg: Sample University'
1973            '\nmsg: '
1974            '\nSending email from no-reply@waeup.org to otis@stones.de:'
1975            '\nMessage:'
1976            '\nmsg: MIME-Version: 1.0'
1977            '\nmsg: Content-Type: text/plain; charset="us-ascii"'
1978            '\nmsg: Content-Transfer-Encoding: 7bit'
1979            '\nmsg: From: Administrator <no-reply@waeup.org>'
1980            '\nmsg: To: Otis Stone <otis@stones.de>'
1981            '\nmsg: Reply-To: Administrator <contact@waeup.org>'
1982            '\nmsg: Subject: Request for referee report from Sample University'
1983            '\nmsg: '
1984            '\nmsg: Dear Otis Stone,'
1985            '\nmsg: '
1986            '\nmsg: The candidate with Id app%s_<6-DIGITS> and name John Anthony Tester applied to'
1987            '\nmsg: the Sample University to study Unnamed Certificate for the %s/%s session.'
1988            '\nmsg: The candidate has listed you as referee. You are thus required to kindly use'
1989            '\nmsg: the link below to provide your referral remarks on or before'
1990            '\nmsg: <YYYY-MM-DD hh:mm:ss>.<6-DIGITS>+00:00.'
1991            '\nmsg: '
1992            '\nmsg: Report link: http://localhost/app/mandate?mandate_id=%s'
1993            '\nmsg: '
1994            '\nmsg: Thank You'
1995            '\nmsg: '
1996            '\nmsg: The Secretary'
1997            '\nmsg: Post Graduate School'
1998            '\nmsg: Sample University'
1999            '\nmsg: '
2000            % (session_1, session_1, session_2, mandate_id_0,
2001               session_1, session_1, session_2, mandate_id_1,),
2002            self.get_fake_smtp_output()
2003            )
2004        self.assertTrue(
2005            'Application submitted' in self.browser.contents)
2006        self.assertTrue(
2007            'Form has been successfully submitted and 2 '
2008            'invitation emails were sent.' in self.browser.contents)
2009        logfile = os.path.join(
2010            self.app['datacenter'].storage, 'logs', 'applicants.log')
2011        logcontent = open(logfile).read()
2012        self.assertTrue(
2013            '%s - applicants.browser.ApplicantEditFormPage - %s - '
2014            'email sent: otis@stones.de' %
2015            (self.applicant.applicant_id, self.applicant.applicant_id)
2016            in logcontent)
2017        self.assertTrue(referee1.email_sent)
2018        self.assertTrue(referee2.email_sent)
2019        # If the form is being resubmitted, no more emails will be sent
2020        IWorkflowState(self.applicant).setState('paid')
2021        self.applicant.locked = False
2022        self.browser.open(self.edit_path)
2023        self.browser.getControl(name="confirm_passport").value = True
2024        self.browser.getControl("Finally Submit").click()
2025        self.assertTrue(
2026            'Form has been successfully submitted and 0 '
2027            'invitation emails were sent.' in self.browser.contents)
2028        return
2029
2030    def test_remind_referees(self):
2031        self.applicant.lastname = u'Mitchell'
2032        IWorkflowState(self.applicant).setState('submitted')
2033        # Add two referees
2034        referee1 = RefereeEntry()
2035        referee2 = RefereeEntry()
2036        referee1.name = u'Linda Tree'
2037        referee1.email = 'linda@forest.de'
2038        referee2.name = u'Otis Stone'
2039        referee2.email = 'otis@stones.de'
2040        referee1.email_sent = True
2041        referee2.email_sent = True
2042        self.applicant.referees = [referee1, referee2]
2043        # Managers can remind referees
2044        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
2045        self.browser.open(self.view_path)
2046        self.browser.getLink("Remind referees").click()
2047        self.assertTrue('2 referee(s) have been reminded by email.'
2048            in self.browser.contents)
2049        logfile = os.path.join(
2050            self.app['datacenter'].storage, 'logs', 'applicants.log')
2051        logcontent = open(logfile).read()
2052        self.assertTrue(
2053            'zope.mgr - applicants.browser.RefereesRemindPage - %s - '
2054            'email sent: otis@stones.de' % self.applicant.applicant_id
2055            in logcontent)
2056        if self.app['mandates'].values()[0].params['name'] == 'Linda Tree':
2057            mandate_id_0 = self.app['mandates'].keys()[0]
2058            mandate_id_1 = self.app['mandates'].keys()[1]
2059        else:
2060            mandate_id_0 = self.app['mandates'].keys()[1]
2061            mandate_id_1 = self.app['mandates'].keys()[0]
2062        self.assertMatches(
2063            'Sending email from no-reply@waeup.org to linda@forest.de:'
2064            '\nMessage:'
2065            '\nmsg: MIME-Version: 1.0\nmsg: Content-Type: text/plain; charset="us-ascii"'
2066            '\nmsg: Content-Transfer-Encoding: 7bit'
2067            '\nmsg: From: Administrator <no-reply@waeup.org>'
2068            '\nmsg: To: Linda Tree <linda@forest.de>'
2069            '\nmsg: Reply-To: Administrator <contact@waeup.org>'
2070            '\nmsg: Subject: Request for referee report from Sample University'
2071            '\nmsg: '
2072            '\nmsg: Dear Linda Tree,'
2073            '\nmsg: '
2074            '\nmsg: The candidate with Id app%s_372052 and name Joan Mitchell applied to'
2075            '\nmsg: the Sample University to study Unnamed Certificate for the %s/%s session.'
2076            '\nmsg: The candidate has listed you as referee. You are thus required to kindly use'
2077            '\nmsg: the link below to provide your referral remarks on or before'
2078            '\nmsg: 2016-08-12 08:32:41.619671+00:00.'
2079            '\nmsg: '
2080            '\nmsg: Report link: http://localhost/app/mandate?mandate_id=%s'
2081            '\nmsg: '
2082            '\nmsg: Thank You'
2083            '\nmsg: '
2084            '\nmsg: The Secretary'
2085            '\nmsg: Post Graduate School'
2086            '\nmsg: Sample University'
2087            '\nmsg: '
2088            '\nSending email from no-reply@waeup.org to otis@stones.de:'
2089            '\nMessage:'
2090            '\nmsg: MIME-Version: 1.0'
2091            '\nmsg: Content-Type: text/plain; charset="us-ascii"'
2092            '\nmsg: Content-Transfer-Encoding: 7bit'
2093            '\nmsg: From: Administrator <no-reply@waeup.org>'
2094            '\nmsg: To: Otis Stone <otis@stones.de>'
2095            '\nmsg: Reply-To: Administrator <contact@waeup.org>'
2096            '\nmsg: Subject: Request for referee report from Sample University'
2097            '\nmsg: '
2098            '\nmsg: Dear Otis Stone,'
2099            '\nmsg: '
2100            '\nmsg: The candidate with Id app%s_<6-DIGITS> and name Joan Mitchell applied to'
2101            '\nmsg: the Sample University to study Unnamed Certificate for the %s/%s session.'
2102            '\nmsg: The candidate has listed you as referee. You are thus required to kindly use'
2103            '\nmsg: the link below to provide your referral remarks on or before'
2104            '\nmsg: <YYYY-MM-DD hh:mm:ss>.<6-DIGITS>+00:00.'
2105            '\nmsg: '
2106            '\nmsg: Report link: http://localhost/app/mandate?mandate_id=%s'
2107            '\nmsg: '
2108            '\nmsg: Thank You'
2109            '\nmsg: '
2110            '\nmsg: The Secretary'
2111            '\nmsg: Post Graduate School'
2112            '\nmsg: Sample University'
2113            '\nmsg: '
2114            % (session_1, session_1, session_2, mandate_id_0,
2115               session_1, session_1, session_2, mandate_id_1,),
2116            self.get_fake_smtp_output()
2117            )
2118        # If a report exists, only one email is being sent to Otis Stone.
2119        report = createObject(u'waeup.ApplicantRefereeReport')
2120        report.r_id = 'any_id'
2121        report.name = u'Liiiinda Tree'
2122        report.email = 'linda@forest.de'
2123        self.applicant[report.r_id] = report
2124        self.browser.open(self.view_path)
2125        self.browser.getLink("Remind referees").click()
2126        self.assertTrue('1 referee(s) have been reminded by email.'
2127            in self.browser.contents)
2128        return
Note: See TracBrowser for help on using the repository browser.