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

Last change on this file since 16955 was 16801, checked in by Henrik Bettermann, 3 years ago

Adjust test.

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