blob: 8981ee8437604b78f95b70a832aec3eea2e8cf30 [file] [log] [blame]
# Copyright 2014 the Melange authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Unit tests for proposal related views."""
import json
import mock
import httplib
import unittest
from google.appengine.ext import ndb
from melange.models import organization as org_model
from melange.request import exception
from melange.request import links
from melange.utils import rich_bool
from seeder import profile as profile_seeder
from seeder import program as program_seeder
from seeder import sponsor as sponsor_seeder
from seeder import user as user_seeder
from soc.modules.gsoc.views.helper import request_data
from summerofcode.logic import proposal as proposal_logic
from summerofcode.models import proposal as proposal_model
from summerofcode.views import proposal as proposal_view
from tests import org_utils
from tests import profile_utils
from tests import test_utils
from tests.utils import proposal_utils
def _getListProposalsForHostUrl(program):
"""Returns URL to 'List Proposals For Program Host' page for
the specified program.
Args:
program: Program entity.
Returns:
The URL to 'List Proposals For Program Host' page.
"""
return '/gsoc/proposal/list/host/%s' % program.key().name()
def _getListProposalsForOrgMemberUrl(program):
"""Returns URL to 'List Proposals For Organization Member' page for
the specified program.
Args:
program: Program entity.
Returns:
The URL to 'List Proposals For Organization Member' page.
"""
return '/gsoc/proposal/list/org/%s' % program.key().name()
def _getListProposalsForStudentUrl(program):
"""Returns URL to 'List Proposals For Student' page for the specified program.
Args:
program: Program entity.
Returns:
The URL to 'List Proposals For Student' page.
"""
return '/gsoc/proposal/list/student/%s' % program.key().name()
def _getPickOrganizationToSubmitProposalUrl(program):
"""Returns URL to 'Pick Organization To Submit Proposal' page for
the specified program.
Args:
program: Program entity.
Returns:
The URL to 'Pick Organization To Submit Proposal' page.
"""
return '/gsoc/proposal/pick/%s' % program.key().name()
def _getProposalSubmitUrl(org):
"""Returns URL to 'Proposal Submit' page for the specified organization.
Args:
org: Organization entity.
Returns:
The URL to 'Proposal Submit' page.
"""
return '/gsoc/proposal/submit/%s' % org.key.id()
def _getProposalEditUrl(proposal, validated=False):
"""Returns URL to 'Proposal Edit' page for the specified proposal.
Args:
proposal: Proposal entity.
validated: A bool telling whether the URL is validated or not.
Returns:
The URL to 'Proposal Edit' page.
"""
return ('/gsoc/proposal/edit/%s/%s' % (
proposal.key.parent().id(), proposal.key.id()) +
('?validated=True' if validated else ''))
def _getProposalScoreUrl(proposal):
"""Returns URL to 'Proposal Score' handler for the specified proposal.
Args:
proposal: Proposal entity.
Returns:
The URL to 'Proposal Score' handler.
"""
return '/gsoc/proposal/score/%s/%s' % (
proposal.key.parent().id(), proposal.key.id())
def getProposalReviewAsStudentUrl(proposal, validated=False):
"""Returns URL to 'Proposal Review As Student' page for the
specified proposal.
Args:
proposal: Proposal entity.
validated: A bool telling whether the URL is validated or not.
Returns:
The URL to 'Proposal Edit' page.
"""
return ('/gsoc/proposal/review/student/%s/%s' % (
proposal.key.parent().id(), proposal.key.id()) +
('?validated=True' if validated else ''))
_NUMBER_OF_ACCEPTED_ORGS = 2
class PickOrganizationToSubmitProposalPageTest(test_utils.GSoCDjangoTestCase):
"""Unit tests for PickOrganizationToSubmitProposalPage class."""
def setUp(self):
"""See unittest.TestCase.setUp for specification."""
self.init()
self.timeline_helper.studentSignup()
# seed a student
user = user_seeder.seedUser()
profile_utils.loginNDB(user)
profile_seeder.seedStudent(self.program, user=user)
def testPageLoads(self):
"""Tests that the page loads properly."""
response = self.get(_getPickOrganizationToSubmitProposalUrl(self.program))
self.assertResponseOK(response)
def testListData(self):
"""Tests that correct list data is loaded."""
# seed some accepted organizations
for _ in range(_NUMBER_OF_ACCEPTED_ORGS):
org_utils.seedSOCOrganization(
self.program.key(), status=org_model.Status.ACCEPTED)
# seed one rejected organization
org_utils.seedSOCOrganization(
self.program.key(), status=org_model.Status.REJECTED)
list_data = self.getListData(
_getPickOrganizationToSubmitProposalUrl(self.program), 0)
# check that accepted organizations are listed
self.assertEqual(len(list_data), _NUMBER_OF_ACCEPTED_ORGS)
_TEST_PROPOSAL_SUBMIT_POST_DATA = {
'title': 'Test Title',
'abstract': 'Test abstract',
'full_content': 'Test Content',
'additional_info': 'http://www.addtional.info.com',
}
class ProposalSubmitPageTest(test_utils.GSoCDjangoTestCase):
"""Unit tests for ProposalSubmitPage class."""
def setUp(self):
"""See unittest.TestCase.setUp for specification."""
self.init()
self.timeline_helper.studentSignup()
# seed a student
user = user_seeder.seedUser()
profile_utils.loginNDB(user)
student_data_properties = {'enrollment_form': self.createBlob('unused')}
self.student = profile_seeder.seedStudent(
self.program, user=user,
student_data_properties=student_data_properties)
# organization is accepted so that students can apply to it
self.org.status = org_model.Status.ACCEPTED
self.org.put()
def testPageLoads(self):
"""Tests that the page loads properly."""
response = self.get(_getProposalSubmitUrl(self.org))
self.assertResponseOK(response)
def testProposalCreated(self):
"""Tests that a new proposal is created correctly."""
postdata = _TEST_PROPOSAL_SUBMIT_POST_DATA.copy()
postdata['visibility'] = proposal_view._VISIBILITY_ORG_MEMBER_ID
response = self.post(
_getProposalSubmitUrl(self.org), postdata=postdata)
# check that a new proposal is created
proposal = proposal_model.Proposal.query(ancestor=self.student.key).get()
self.assertResponseRedirect(
response, url=_getProposalEditUrl(proposal, validated=True))
def testDefaultProposalRanking(self):
"""Tests that a new proposal has correct default rank in the student's
proposal preferences.
"""
postdata = _TEST_PROPOSAL_SUBMIT_POST_DATA.copy()
postdata['visibility'] = proposal_view._VISIBILITY_ORG_MEMBER_ID
response = self.post(
_getProposalSubmitUrl(self.org), postdata=postdata)
proposal = proposal_model.Proposal.query(ancestor=self.student.key).get()
student = self.student.key.get()
submitted_proposals_list = student.student_data.preference_of_proposals
self.assertEqual(proposal.key, submitted_proposals_list[-1])
def testNotificationsSent(self):
"""Tests that notifications are sent when proposal is submitted."""
# seed a mentor and an organization admin
mentor = profile_seeder.seedProfile(
self.program.key(), mentor_for=[self.org.key])
admin = profile_seeder.seedProfile(
self.program.key(), admin_for=[self.org.key])
postdata = _TEST_PROPOSAL_SUBMIT_POST_DATA.copy()
postdata['visibility'] = proposal_view._VISIBILITY_ORG_MEMBER_ID
self.post(_getProposalSubmitUrl(self.org), postdata=postdata)
# check that emails were sent to the mentor and organization admin
self.assertEmailSent(bcc=mentor.contact.email)
self.assertEmailSent(bcc=admin.contact.email)
# TODO(daniel): check that emails are not sent to those who opt out
_OTHER_PROPOSAL_SUBMIT_POST_DATA = {
'title': u'Other Title',
'abstract': u'Other abstract',
'full_content': u'Other Content',
'additional_info': u'http://www.other.additional.info.com/',
}
class ProposalEditPageTest(test_utils.GSoCDjangoTestCase):
"""Unit tests for ProposalEditPage class."""
def setUp(self):
"""See unittest.TestCase.setUp for specification."""
self.init()
self.timeline_helper.studentSignup()
# seed a student
user = user_seeder.seedUser()
profile_utils.loginNDB(user)
student = profile_seeder.seedStudent(self.program, user=user)
# organization is accepted so that students can apply to it
self.org.status = org_model.Status.ACCEPTED
self.org.put()
self.proposal = proposal_utils.seedNDBProposal(
student.key, self.program.key(), org_key=self.org.key)
def testPageLoads(self):
"""Tests that the page loads properly."""
response = self.get(_getProposalEditUrl(self.proposal))
self.assertResponseOK(response)
def testProposalUpdated(self):
"""Tests that the proposal is updated correctly."""
postdata = _OTHER_PROPOSAL_SUBMIT_POST_DATA.copy()
postdata['visibility'] = proposal_view._VISIBILITY_ORG_MEMBER_ID
# expected properties after the edit is complete
expected_properties = self.proposal.to_dict(exclude=['modified_on'])
expected_properties['title'] = _OTHER_PROPOSAL_SUBMIT_POST_DATA['title']
expected_properties['abstract'] = (
_OTHER_PROPOSAL_SUBMIT_POST_DATA['abstract'])
expected_properties['additional_info'] = (
_OTHER_PROPOSAL_SUBMIT_POST_DATA['additional_info'])
expected_properties['visibility'] = proposal_model.Visibility.ORG_MEMBER
revision_number = str(self.proposal.latest_revision.get().revision + 1)
proposal_revision_flat = list(self.proposal.key.flat())
proposal_revision_flat.append(proposal_model.ProposalRevision)
proposal_revision_flat.append(revision_number)
latest_revision_property = proposal_model.Proposal.latest_revision._name
expected_properties[latest_revision_property] = ndb.Key(
flat=proposal_revision_flat)
response = self.post(
_getProposalEditUrl(self.proposal), postdata=postdata)
self.assertResponseRedirect(
response, url=getProposalReviewAsStudentUrl(
self.proposal, validated=True))
# check that the proposal is updated correctly
proposal = self.proposal.key.get()
actual_properties = proposal.to_dict(exclude=['modified_on'])
self.assertDictEqual(actual_properties, expected_properties)
self.assertEqual(
proposal.content, _OTHER_PROPOSAL_SUBMIT_POST_DATA['full_content'])
class ListProposalsForHostTest(test_utils.GSoCDjangoTestCase):
"""Unit tests for ListProposalsForHost class."""
def setUp(self):
"""See unittest.TestCase.setUp for specification."""
self.init()
def testPageLoads(self):
"""Tests that the page loads properly."""
user = user_seeder.seedUser(host_for=[self.program])
profile_utils.loginNDB(user)
response = self.get(_getListProposalsForHostUrl(self.program))
self.assertResponseOK(response)
def testListData(self):
"""Tests that correct list data is loaded."""
user = user_seeder.seedUser(host_for=[self.program])
profile_utils.loginNDB(user)
# seed other organizations
org_one = org_utils.seedSOCOrganization(self.program.key())
org_two = org_utils.seedSOCOrganization(self.program.key())
# seed a few proposals
student_one = profile_seeder.seedStudent(self.program)
proposal_utils.seedNDBProposal(
student_one.key, self.program.key(), org_key=self.org.key,
status=proposal_model.Status.ACCEPTED)
proposal_utils.seedNDBProposal(
student_one.key, self.program.key(), org_key=org_one.key)
proposal_utils.seedNDBProposal(
student_one.key, self.program.key(), org_key=org_two.key)
student_two = profile_seeder.seedStudent(self.program)
proposal_utils.seedNDBProposal(
student_two.key, self.program.key(), org_key=org_one.key,
status=proposal_model.Status.WITHDRAWN)
proposal_utils.seedNDBProposal(
student_two.key, self.program.key(), org_key=org_one.key,
is_ignored=True)
# seed another program
other_program = program_seeder.seedGSoCProgram()
# seed a proposal for the other program
other_org = org_utils.seedSOCOrganization(other_program.key())
other_student = profile_seeder.seedStudent(other_program)
proposal_utils.seedNDBProposal(
other_student.key, other_program.key(), org_key=other_org.key)
list_data = self.getListData(
_getListProposalsForHostUrl(self.program), 0)
# check that all proposals save the last one are included
self.assertEqual(len(list_data), 5)
class ListProposalsForStudentTest(test_utils.GSoCDjangoTestCase):
"""Unit tests for ListProposalsForStudent class."""
def setUp(self):
"""See unittest.TestCase.setUp for specification."""
self.init()
def testPageLoads(self):
"""Tests that the page loads properly."""
user = user_seeder.seedUser()
profile_utils.loginNDB(user)
profile_seeder.seedStudent(self.program, user=user)
response = self.get(_getListProposalsForStudentUrl(self.program))
self.assertResponseOK(response)
def testListData(self):
"""Tests that correct list data is loaded."""
user = user_seeder.seedUser()
profile_utils.loginNDB(user)
profile = profile_seeder.seedStudent(self.program, user=user)
# seed another organization
other_org = org_utils.seedSOCOrganization(self.program.key())
# seed a couple of proposals
proposal_utils.seedNDBProposal(
profile.key, self.program.key(), org_key=self.org.key)
proposal_utils.seedNDBProposal(
profile.key, self.program.key(), org_key=other_org.key)
# seed another student with a proposal
other_student = profile_seeder.seedStudent(self.program)
proposal_utils.seedNDBProposal(
other_student.key, self.program.key(), org_key=self.org.key)
list_data = self.getListData(
_getListProposalsForStudentUrl(self.program), 0)
# check that only two proposals are listed
self.assertEqual(len(list_data), 2)
def testRankingProposal(self):
"""Tests that ranking of proposals is done correctly."""
user = user_seeder.seedUser()
profile_utils.loginNDB(user)
profile = profile_seeder.seedStudent(self.program, user=user)
# seed a couple of organizations
other_org1 = org_utils.seedSOCOrganization(self.program.key())
other_org2 = org_utils.seedSOCOrganization(self.program.key())
# seed a total of three of proposals
proposal_utils.seedNDBProposal(
profile.key, self.program.key(), org_key=self.org.key)
proposal_utils.seedNDBProposal(
profile.key, self.program.key(), org_key=other_org1.key)
proposal_utils.seedNDBProposal(
profile.key, self.program.key(), org_key=other_org2.key)
old_list_data = self.getListData(
_getListProposalsForStudentUrl(self.program), 0)
changed_data = {}
changed_data[old_list_data[0]['columns']['key']] = {'rank': old_list_data[1]['columns']['rank']}
changed_data[old_list_data[1]['columns']['key']] = {'rank': old_list_data[2]['columns']['rank']}
data = json.dumps(changed_data)
postdata = {
'data': data,
'button_id': 'save',
'idx': 0,
}
response = self.post(
_getListProposalsForStudentUrl(self.program), postdata=postdata)
self.assertResponseOK(response)
new_list_data = self.getListData(
_getListProposalsForStudentUrl(self.program), 0)
self.assertEqual(new_list_data[0]['columns']['rank'], old_list_data[1]['columns']['rank'])
self.assertEqual(new_list_data[1]['columns']['rank'], old_list_data[2]['columns']['rank'])
self.assertEqual(new_list_data[2]['columns']['rank'], old_list_data[0]['columns']['rank'])
_TEST_SCORE_VALUE = 2
class ListProposalsForOrgMemberTest(test_utils.GSoCDjangoTestCase):
"""Unit tests for ListProposalsForOrgMember class."""
def setUp(self):
"""See unittest.TestCase.setUp for specification."""
self.init()
def testPageLoads(self):
"""Tests that the page loads properly."""
user = user_seeder.seedUser()
profile_utils.loginNDB(user)
profile_seeder.seedProfile(
self.program.key(), user=user, mentor_for=[self.org.key])
response = self.get(_getListProposalsForOrgMemberUrl(self.program))
self.assertResponseOK(response)
def testListData(self):
"""Tests that correct list data is loaded."""
# seed another organization
org_one = org_utils.seedSOCOrganization(self.program.key())
user = user_seeder.seedUser()
profile_utils.loginNDB(user)
profile = profile_seeder.seedProfile(
self.program.key(), user=user, mentor_for=[self.org.key, org_one.key])
# seed a couple of proposals
student = profile_seeder.seedStudent(self.program)
proposal_utils.seedNDBProposal(
student.key, self.program.key(), org_key=self.org.key,
scores=[proposal_model.Score(
author=profile.key, value=_TEST_SCORE_VALUE)])
proposal_utils.seedNDBProposal(
student.key, self.program.key(), org_key=org_one.key)
# seed another organization with a proposal
org_two = org_utils.seedSOCOrganization(self.program.key())
proposal_utils.seedNDBProposal(
student.key, self.program.key(), org_key=org_two.key)
list_data = self.getListData(
_getListProposalsForOrgMemberUrl(self.program), 0)
# check that only two proposals are listed
self.assertEqual(len(list_data), 2)
_TEST_PROPOSAL_LIMIT = 3
class ProposalLimitNotReachedAccessCheckerTest(unittest.TestCase):
"""Unit tests for ProposalLimitNotReachedAccessChecker class."""
def setUp(self):
"""See unittest.TestCase.setUp for specification."""
sponsor = sponsor_seeder.seedSponsor()
self.program = program_seeder.seedGSoCProgram(
sponsor_key=sponsor.key(), apps_tasks_limit=_TEST_PROPOSAL_LIMIT)
user = user_seeder.seedUser()
profile_utils.loginNDB(user)
self.profile = profile_seeder.seedStudent(self.program, user=user)
kwargs = {
'sponsor': sponsor.key().name(),
'program': self.program.program_id
}
self.data = request_data.RequestData(None, None, kwargs)
def testLimitNotReachedAccessGranted(self):
"""Tests that access is granted if limit is not reached."""
# seed limit - 1 proposals
for _ in range(_TEST_PROPOSAL_LIMIT - 1):
proposal_utils.seedNDBProposal(self.profile.key, self.program.key())
access_checker = proposal_view.ProposalLimitNotReachedAccessChecker()
access_checker.checkAccess(self.data, None)
def testLimitReachedAccessGranted(self):
"""Tests that access is granted if limit is reached."""
# seed limit proposals
for _ in range(_TEST_PROPOSAL_LIMIT):
proposal_utils.seedNDBProposal(self.profile.key, self.program.key())
access_checker = proposal_view.ProposalLimitNotReachedAccessChecker()
with self.assertRaises(exception.UserError) as context:
access_checker.checkAccess(self.data, None)
self.assertEqual(context.exception.status, httplib.FORBIDDEN)
class EnrollmentFormSubmittedAccessCheckerTest(test_utils.SoCTestCase):
"""Unit tests for EnrollmentFormSubmittedAccessChecker class."""
def setUp(self):
"""See unittest.TestCase.setUp for specification."""
self.init()
sponsor = sponsor_seeder.seedSponsor()
self.program = program_seeder.seedGSoCProgram(
sponsor_key=sponsor.key(), apps_tasks_limit=_TEST_PROPOSAL_LIMIT)
user = user_seeder.seedUser()
profile_utils.loginNDB(user)
self.profile = profile_seeder.seedStudent(self.program, user=user)
kwargs = {
'sponsor': sponsor.key().name(),
'program': self.program.program_id
}
self.data = request_data.RequestData(None, None, kwargs)
def testEnrollmentFormSubmittedAccessGranted(self):
"""Tests that access is granted when enrollment form is submitted."""
self.profile.student_data.enrollment_form = self.createBlob('unused')
self.profile.put()
access_checker = proposal_view.EnrollmentFormSubmittedAccessChecker()
access_checker.checkAccess(self.data, None)
def testEnrollmentFormNotSubmittedAccessDenied(self):
"""Tests that access is denied when enrollment form is not submitted."""
access_checker = proposal_view.EnrollmentFormSubmittedAccessChecker()
with self.assertRaises(exception.UserError) as context:
access_checker.checkAccess(self.data, None)
self.assertEqual(context.exception.status, httplib.FORBIDDEN)
# Test enrollment form URL
enrollment_url = links.ABSOLUTE_LINKER.program(
self.data.program, 'gsoc_enrollment_form', secure=True)
self.assertIn(enrollment_url, context.exception.message)
_TEST_SCORE_VALUE = 4
_TEST_UPDATED_SCORE_VALUE = 1
class ProposalScoreTest(test_utils.GSoCDjangoTestCase):
"""Unit tests for ProposalScore class."""
def setUp(self):
"""See unittest.TestCase.setUp for specification."""
self.init()
student = profile_seeder.seedStudent(self.program)
self.proposal = proposal_utils.seedNDBProposal(
student.key, self.program.key(), org_key=self.org.key)
def testSetScore(self):
"""Test that a score is set properly."""
user = user_seeder.seedUser()
profile_utils.loginNDB(user)
profile = profile_seeder.seedProfile(
self.program.key(), user=user, mentor_for=[self.org.key])
postdata = {'value': str(_TEST_SCORE_VALUE)}
response = self.post(_getProposalScoreUrl(self.proposal), postdata=postdata)
self.assertResponseOK(response)
# check that the score is set
proposal = self.proposal.key.get()
score = proposal.scores[0]
self.assertEqual(score.value, _TEST_SCORE_VALUE)
self.assertEqual(score.author, profile.key)
def testCleanScore(self):
"""Test that a score is cleaned properly."""
user = user_seeder.seedUser()
profile_utils.loginNDB(user)
profile = profile_seeder.seedProfile(
self.program.key(), user=user, mentor_for=[self.org.key])
# set an existing score
self.proposal.scores.append(
proposal_model.Score(author=profile.key, value=_TEST_SCORE_VALUE))
self.proposal.put()
postdata = {'value': ''}
response = self.post(_getProposalScoreUrl(self.proposal), postdata=postdata)
self.assertResponseOK(response)
# check that no score is set
proposal = self.proposal.key.get()
self.assertListEqual(proposal.scores, [])
def testUpdateScore(self):
"""Test that a score is updated properly."""
user = user_seeder.seedUser()
profile_utils.loginNDB(user)
profile = profile_seeder.seedProfile(
self.program.key(), user=user, mentor_for=[self.org.key])
# set an existing score
self.proposal.scores.append(
proposal_model.Score(author=profile.key, value=_TEST_SCORE_VALUE))
self.proposal.put()
postdata = {'value': str(_TEST_UPDATED_SCORE_VALUE)}
response = self.post(_getProposalScoreUrl(self.proposal), postdata=postdata)
self.assertResponseOK(response)
# check that the score is updated
proposal = self.proposal.key.get()
score = proposal.scores[0]
self.assertEqual(score.value, _TEST_UPDATED_SCORE_VALUE)
self.assertEqual(score.author, profile.key)
class IsUrlProposalEditableAccessCheckerTest(unittest.TestCase):
"""Unit tests for IsUrlProposalEditableAccessChecker class."""
def setUp(self):
"""See unittest.TestCase.setUp for specification."""
sponsor = sponsor_seeder.seedSponsor()
program = program_seeder.seedGSoCProgram(sponsor_key=sponsor.key())
student = profile_seeder.seedStudent(program)
proposal = proposal_utils.seedNDBProposal(student.key, program.key())
kwargs = {
'sponsor': sponsor.key().name(),
'program': program.program_id,
'user': student.profile_id,
'id': proposal.key.id()
}
self.data = request_data.RequestData(None, None, kwargs)
@mock.patch.object(
proposal_logic, 'canStudentUpdateProposal', return_value=rich_bool.FALSE)
def testStudentCannotUpdateProposalAccessDenied(self, mock_func):
"""Tests that access is denied if student cannot edit proposal."""
access_checker = proposal_view.IsUrlProposalEditableAccessChecker()
with self.assertRaises(exception.UserError) as context:
access_checker.checkAccess(self.data, None)
self.assertEqual(context.exception.status, httplib.FORBIDDEN)
@mock.patch.object(
proposal_logic, 'canStudentUpdateProposal', return_value=rich_bool.TRUE)
def testStudentCanUpdateProposalAccessGranted(self, mock_func):
"""Tests that access is granted if student can edit proposal."""
access_checker = proposal_view.IsUrlProposalEditableAccessChecker()
access_checker.checkAccess(self.data, None)