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

Last change on this file since 17009 was 17009, checked in by Henrik Bettermann, 2 years ago

Do not require session configuration object for application payments.

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