source: main/waeup.kofa/branches/uli-py3/src/waeup/kofa/applicants/tests/test_browser.py @ 17421

Last change on this file since 17421 was 15943, checked in by Henrik Bettermann, 5 years ago

Provide components for file uploads in the applicants section.

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