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

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

Use email address as identifier for reports. Names can be changed by referees but email addresses must not be changed.

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