def _setup_program_and_program_cert(self, program_type): self.program = ProgramFactory(site=self.site) self.program.type = program_type self.program.type_slug = slugify(program_type) self.program.save() self.program_cert = ProgramCertificateFactory( site=self.site, program_uuid=self.program.uuid)
def test_successful_generation(self): ProgramFactory.create( title="edX Demonstration Program", course_runs=[self.new_course_run], authoring_organizations=self.orgs, site=self.site, ) call_command(command_name=COMMAND) assert len(ProgramCertificate.objects.all()) > 0
def test_list_program_uuid_filtering(self): """ Verify the endpoint returns data for all UserCredentials in the given program. """ # Course run 1 is in a program, course run 2 is not course1_run = CourseRunFactory() course2_run = CourseRunFactory() program = ProgramFactory(course_runs=[course1_run]) program_certificate = ProgramCertificateFactory( site=self.site, program_uuid=program.uuid) course1_certificate = CourseCertificateFactory( site=self.site, course_id=course1_run.key) course2_certificate = CourseCertificateFactory( site=self.site, course_id=course2_run.key) # Create some credentials related to the program course1_cred = UserCredentialFactory(credential=course1_certificate) program_creds = UserCredentialFactory.create_batch( 3, credential=program_certificate) expected = [course1_cred] + program_creds # Create some more credentials that we don't expect to see returned UserCredentialFactory.create_batch(3) UserCredentialFactory(credential=course2_certificate) self.authenticate_user(self.user) self.add_user_permission(self.user, 'view_usercredential') response = self.client.get(self.list_path + '?program_uuid={}'.format(program.uuid)) self.assertEqual(response.status_code, 200) self.assertEqual(response.data['results'], self.serialize_user_credential(expected, many=True))
def setUp(self): super().setUp() dump_random_state() user = UserFactory(username=self.USERNAME) self.client.login(username=user.username, password=USER_PASSWORD) self.program = ProgramFactory(site=self.site)
def setUp(self): self.site = SiteFactory() self.program = ProgramFactory(site=self.site) self.certificate = self.cert_factory.create(program_uuid=self.program.uuid, site=self.site) self.username = '******' self.user = UserFactory(username=self.username) self.user_cred = self.issuer.issue_credential(self.certificate, self.username) self.attributes = [{"name": "whitelist_reason", "value": "Reason for whitelisting."}]
def setUp(self): super().setUp() dump_random_state() self.user = UserFactory(username=self.MOCK_USER_DATA["username"]) self.client.login(username=self.user.username, password=USER_PASSWORD) self.course = CourseFactory(site=self.site) self.course_runs = CourseRunFactory.create_batch(3, course=self.course) self.user_grade_low = UserGradeFactory( username=self.MOCK_USER_DATA["username"], course_run=self.course_runs[0], letter_grade="A", percent_grade=0.70, ) self.user_grade_high = UserGradeFactory( username=self.MOCK_USER_DATA["username"], course_run=self.course_runs[1], letter_grade="C", percent_grade=1.00, ) self.user_grade_revoked_cert = UserGradeFactory( username=self.MOCK_USER_DATA["username"], course_run=self.course_runs[2], letter_grade="B", percent_grade=0.80, ) self.course_certs = [ CourseCertificateFactory(course_id=course_run.key, site=self.site) for course_run in self.course_runs ] self.credential_content_type = ContentType.objects.get(app_label="credentials", model="coursecertificate") self.program_cert = ProgramCertificateFactory(site=self.site) self.program_content_type = ContentType.objects.get(app_label="credentials", model="programcertificate") self.user_credentials = [ UserCredentialFactory( username=self.MOCK_USER_DATA["username"], credential_content_type=self.credential_content_type, credential=course_cert, ) for course_cert in self.course_certs ] self.user_credentials[2].status = UserCredential.REVOKED self.user_credentials[2].save() self.org_names = ["CCC", "AAA", "BBB"] self.orgs = [OrganizationFactory(name=name, site=self.site) for name in self.org_names] self.program = ProgramFactory( course_runs=self.course_runs, authoring_organizations=self.orgs, site=self.site, uuid=self.program_cert.program_uuid, ) self.pcr = ProgramCertRecordFactory(program=self.program, user=self.user) self.pathway = PathwayFactory(site=self.site) self.pathway.programs.set([self.program])
def setUp(self): super().setUp() dump_random_state() self.user = UserFactory(username=self.MOCK_USER_DATA["username"]) self.orgs = [OrganizationFactory.create(name=name, site=self.site) for name in ["TestOrg1", "TestOrg2"]] self.course = CourseFactory.create(site=self.site) self.course_runs = CourseRunFactory.create_batch(2, course=self.course) self.program = ProgramFactory( title="TestProgram1", course_runs=self.course_runs, authoring_organizations=self.orgs, site=self.site ) self.course_certs = [ CourseCertificateFactory.create( course_id=course_run.key, site=self.site, ) for course_run in self.course_runs ] self.program_cert = ProgramCertificateFactory.create(program_uuid=self.program.uuid, site=self.site) self.course_credential_content_type = ContentType.objects.get( app_label="credentials", model="coursecertificate" ) self.program_credential_content_type = ContentType.objects.get( app_label="credentials", model="programcertificate" ) self.course_user_credentials = [ UserCredentialFactory.create( username=self.user.username, credential_content_type=self.course_credential_content_type, credential=course_cert, ) for course_cert in self.course_certs ] self.program_user_credential = UserCredentialFactory.create( username=self.user.username, credential_content_type=self.program_credential_content_type, credential=self.program_cert, ) self.client.login(username=self.user.username, password=USER_PASSWORD)
def setUp(self): super().setUp() dump_random_state() self.user = UserFactory(username=self.USERNAME) self.client.login(username=self.user.username, password=USER_PASSWORD) self.program = ProgramFactory(site=self.site) self.pathway = PathwayFactory(site=self.site, programs=[self.program]) self.pc = ProgramCertificateFactory(site=self.site, program_uuid=self.program.uuid) self.pcr = ProgramCertRecordFactory(program=self.program, user=self.user) self.data = {'username': self.USERNAME, 'pathway_id': self.pathway.id} self.url = reverse('records:share_program', kwargs={'uuid': self.program.uuid.hex}) mail.outbox = []
def setUp(self): super().setUp() self.user = UserFactory(username=self.USERNAME) self.client.login(username=self.user.username, password=USER_PASSWORD) self.program = ProgramFactory(site=self.site) self.pathway = PathwayFactory(site=self.site, programs=[self.program]) self.pc = ProgramCertificateFactory(site=self.site, program_uuid=self.program.uuid) self.user_credential = UserCredentialFactory(username=self.USERNAME, credential=self.pc) self.data = {"username": self.USERNAME, "pathway_id": self.pathway.id} self.url = reverse("records:send_program", kwargs={"uuid": self.program.uuid.hex}) mail.outbox = []
def test_get_program_details(self): program_uuid = uuid.uuid4() unused_program_uuid = uuid.uuid4() program = ProgramFactory.create(uuid=program_uuid, site=self.site) details = get_program_details_by_uuid(uuid=program_uuid, site=program.site) self.assertIsNotNone(details) self.assertIsInstance(details, ProgramDetails) details = get_program_details_by_uuid(uuid=unused_program_uuid, site=program.site) self.assertIsNone(details)
def test_multiple_programs(self): """ Test that multiple programs can appear, in progress and completed """ # Create a second program, and delete the first one's certificate new_course = CourseFactory.create(site=self.site) new_course_run = CourseRunFactory.create(course=new_course) new_program = ProgramFactory.create(title='ZTestProgram', course_runs=[new_course_run], authoring_organizations=self.orgs, site=self.site) new_course_cert = CourseCertificateFactory.create(course_id=new_course_run.key, site=self.site) new_program_cert = ProgramCertificateFactory.create(program_uuid=new_program.uuid, site=self.site) # Make a new user credential UserCredentialFactory.create( username=self.user.username, credential_content_type=self.program_credential_content_type, credential=new_course_cert ) # Make a new program credential UserCredentialFactory.create( username=self.user.username, credential_content_type=self.program_credential_content_type, credential=new_program_cert ) self.program_user_credential.delete() response = self.client.get(reverse('records:index')) self.assertEqual(response.status_code, 200) program_data = json.loads(response.context_data['programs']) expected_program_data = [ { 'name': self.program.title, 'partner': 'TestOrg1, TestOrg2', 'uuid': self.program.uuid.hex, 'type': slugify(self.program.type), 'completed': False, 'empty': False, }, { 'name': new_program.title, 'partner': 'TestOrg1, TestOrg2', 'uuid': new_program.uuid.hex, 'type': slugify(new_program.type), 'completed': True, 'empty': False, } ] self.assertEqual(program_data, expected_program_data)
def setUp(self): super().setUp() self.user = UserFactory(username=self.MOCK_USER_DATA['username']) self.client.login(username=self.user.username, password=USER_PASSWORD) self.course = CourseFactory(site=self.site) self.course_runs = [ CourseRunFactory(course=self.course) for _ in range(3) ] self.user_grade_low = UserGradeFactory( username=self.MOCK_USER_DATA['username'], course_run=self.course_runs[0], letter_grade='A', percent_grade=0.70) self.user_grade_high = UserGradeFactory( username=self.MOCK_USER_DATA['username'], course_run=self.course_runs[1], letter_grade='C', percent_grade=1.00) self.user_grade_revoked_cert = UserGradeFactory( username=self.MOCK_USER_DATA['username'], course_run=self.course_runs[2], letter_grade='B', percent_grade=.80) self.course_certs = [ CourseCertificateFactory(course_id=course_run.key, site=self.site) for course_run in self.course_runs ] self.credential_content_type = ContentType.objects.get( app_label='credentials', model='coursecertificate') self.user_credentials = [ UserCredentialFactory( username=self.MOCK_USER_DATA['username'], credential_content_type=self.credential_content_type, credential=course_cert) for course_cert in self.course_certs ] self.user_credentials[2].status = UserCredential.REVOKED self.org_names = ['CCC', 'AAA', 'BBB'] self.orgs = [ OrganizationFactory(name=name, site=self.site) for name in self.org_names ] self.program = ProgramFactory(course_runs=self.course_runs, authoring_organizations=self.orgs, site=self.site)
def test_create(self): program = ProgramFactory(site=self.site) program_certificate = ProgramCertificateFactory( site=self.site, program_uuid=program.uuid) expected_username = self.user.username expected_attribute_name = 'fake-name' expected_attribute_value = 'fake-value' data = { 'username': expected_username, 'credential': { 'program_uuid': str(program_certificate.program_uuid) }, 'status': 'awarded', 'attributes': [{ 'name': expected_attribute_name, 'value': expected_attribute_value, }], } # Verify users without the add permission are denied access self.assert_access_denied(self.user, 'post', self.list_path, data=data) self.authenticate_user(self.user) self.add_user_permission(self.user, 'add_usercredential') response = self.client.post(self.list_path, data=json.dumps(data), content_type=JSON_CONTENT_TYPE) user_credential = UserCredential.objects.last() self.assertEqual(response.status_code, 201) self.assertEqual(response.data, self.serialize_user_credential(user_credential)) self.assertEqual(user_credential.username, expected_username) self.assertEqual(user_credential.credential, program_certificate) self.assertEqual(user_credential.attributes.count(), 1) attribute = user_credential.attributes.first() self.assertEqual(attribute.name, expected_attribute_name) self.assertEqual(attribute.value, expected_attribute_value)
def test_create(self): program = ProgramFactory(site=self.site) program_certificate = ProgramCertificateFactory( site=self.site, program_uuid=program.uuid) expected_username = self.user.username expected_attribute_name = "fake-name" expected_attribute_value = "fake-value" data = { "username": expected_username, "credential": { "program_uuid": str(program_certificate.program_uuid) }, "status": "awarded", "attributes": [{ "name": expected_attribute_name, "value": expected_attribute_value, }], } # Verify users without the add permission are denied access self.assert_access_denied(self.user, "post", self.list_path, data=data) self.authenticate_user(self.user) self.add_user_permission(self.user, "add_usercredential") response = self.client.post(self.list_path, data=json.dumps(data), content_type=JSON_CONTENT_TYPE) user_credential = UserCredential.objects.last() self.assertEqual(response.status_code, 201) self.assertEqual(response.data, self.serialize_user_credential(user_credential)) self.assertEqual(user_credential.username, expected_username) self.assertEqual(user_credential.credential, program_certificate) self.assertEqual(user_credential.attributes.count(), 1) attribute = user_credential.attributes.first() self.assertEqual(attribute.name, expected_attribute_name) self.assertEqual(attribute.value, expected_attribute_value)
def test_keep_old_data(self): """ Verify that we don't destroy existing but obsolete data. """ # org with a uuid that won't be in data we get back org = OrganizationFactory(site=self.site, uuid='44f0dded-fee9-4dec-a333-b9d8c2c82bd2', key='OldX') program = ProgramFactory(site=self.site, uuid='33f0dded-fee9-4dec-a333-c9d8c2c82bd5', title='Old Program') program.authoring_organizations.add(org) # pylint: disable=no-member self.mock_access_token_response() self.mock_programs_response(self.wrap_programs(self.PROGRAMS)) self.mock_pathways_response(self.wrap_pathways(self.PATHWAYS)) self.call_command() org = Organization.objects.get(uuid='44f0dded-fee9-4dec-a333-b9d8c2c82bd2', key='OldX') # But note that it will no longer be connected to our program program = Program.objects.get(uuid='33f0dded-fee9-4dec-a333-c9d8c2c82bd5') self.assertNotIn(org, program.authoring_organizations.all())
def test_update(self): """ Verify the command updates existing data. """ OrganizationFactory(site=self.site, uuid='33f0dded-fee9-4dec-a333-b9d8c2c82bd2', key='OldX') ProgramFactory(site=self.site, uuid='33f0dded-fee9-4dec-a333-c9d8c2c82bd5', title='Old Program') self.mock_access_token_response() self.mock_programs_response(self.wrap_programs(self.PROGRAMS)) self.mock_pathways_response(self.wrap_pathways(self.PATHWAYS)) self.call_command() self.assertProgramsSaved() self.assertPathwaysSaved() org = Organization.objects.get(uuid='33f0dded-fee9-4dec-a333-b9d8c2c82bd2') self.assertNotEqual(org.key, 'OldX') program = Program.objects.get(uuid='33f0dded-fee9-4dec-a333-c9d8c2c82bd5') self.assertNotEqual(program.title, 'Old Program')
def setUp(self): super().setUp() self.user = UserFactory(username=self.MOCK_USER_DATA['username']) self.orgs = [ OrganizationFactory.create(name=name, site=self.site) for name in ['TestOrg1', 'TestOrg2'] ] self.course = CourseFactory.create(site=self.site) self.course_runs = [ CourseRunFactory.create(course=self.course) for _ in range(2) ] self.program = ProgramFactory(course_runs=self.course_runs, authoring_organizations=self.orgs, site=self.site) self.course_certs = [ CourseCertificateFactory.create( course_id=course_run.key, site=self.site, ) for course_run in self.course_runs ] self.program_cert = ProgramCertificateFactory.create( program_uuid=self.program.uuid, site=self.site) self.course_credential_content_type = ContentType.objects.get( app_label='credentials', model='coursecertificate') self.program_credential_content_type = ContentType.objects.get( app_label='credentials', model='programcertificate') self.course_user_credentials = [ UserCredentialFactory.create( username=self.user.username, credential_content_type=self.course_credential_content_type, credential=course_cert) for course_cert in self.course_certs ] self.program_user_credentials = UserCredentialFactory.create( username=self.user.username, credential_content_type=self.program_credential_content_type, credential=self.program_cert) self.client.login(username=self.user.username, password=USER_PASSWORD)
def test_protected_deletion(self): program = ProgramFactory() ProgramCertRecordFactory(program=program) with self.assertRaises(ProtectedError): program.delete()
class RecordsViewTests(SiteMixin, TestCase): MOCK_USER_DATA = { "username": "******", "name": "Test User", "email": "*****@*****.**", } def setUp(self): super().setUp() dump_random_state() self.user = UserFactory(username=self.MOCK_USER_DATA["username"]) self.orgs = [OrganizationFactory.create(name=name, site=self.site) for name in ["TestOrg1", "TestOrg2"]] self.course = CourseFactory.create(site=self.site) self.course_runs = CourseRunFactory.create_batch(2, course=self.course) self.program = ProgramFactory( title="TestProgram1", course_runs=self.course_runs, authoring_organizations=self.orgs, site=self.site ) self.course_certs = [ CourseCertificateFactory.create( course_id=course_run.key, site=self.site, ) for course_run in self.course_runs ] self.program_cert = ProgramCertificateFactory.create(program_uuid=self.program.uuid, site=self.site) self.course_credential_content_type = ContentType.objects.get( app_label="credentials", model="coursecertificate" ) self.program_credential_content_type = ContentType.objects.get( app_label="credentials", model="programcertificate" ) self.course_user_credentials = [ UserCredentialFactory.create( username=self.user.username, credential_content_type=self.course_credential_content_type, credential=course_cert, ) for course_cert in self.course_certs ] self.program_user_credential = UserCredentialFactory.create( username=self.user.username, credential_content_type=self.program_credential_content_type, credential=self.program_cert, ) self.client.login(username=self.user.username, password=USER_PASSWORD) def _render_records(self, program_data=None, status_code=200): """ Helper method to mock and render a user certificate.""" if program_data is None: program_data = [] with patch("credentials.apps.records.views.RecordsView._get_programs") as get_programs: get_programs.return_value = program_data response = self.client.get(reverse("records:index")) self.assertEqual(response.status_code, status_code) return response def assert_matching_template_origin(self, actual, expected_template_name): expected = select_template([expected_template_name]) self.assertEqual(actual.origin, expected.origin) def test_no_anonymous_access(self): """ Verify that the view rejects non-logged-in users. """ self.client.logout() response = self._render_records(status_code=302) self.assertRegex(response.url, "^/login/.*") def test_normal_access(self): """ Verify that the view works in default case. """ response = self._render_records() response_context_data = response.context_data self.assertContains(response, "My Learner Records") actual_child_templates = response_context_data["child_templates"] self.assert_matching_template_origin(actual_child_templates["footer"], "_footer.html") self.assert_matching_template_origin(actual_child_templates["header"], "_header.html") self.assert_matching_template_origin(actual_child_templates["masquerade"], "_masquerade.html") def test_xss(self): """ Verify that the view protects against xss in translations. """ response = self._render_records( [ { "name": "<xss>", "partner": "XSS", "uuid": "uuid", }, ] ) # Test that the data is parsed from an escaped string self.assertContains( response, "JSON.parse('[{" + "\\u0022name\\u0022: \\u0022\\u003Cxss\\u003E\\u0022, " + "\\u0022partner\\u0022: \\u0022XSS\\u0022, " + "\\u0022uuid\\u0022: \\u0022uuid\\u0022" + "}]')", ) self.assertNotContains(response, "<xss>") def test_help_url(self): """ Verify that the records help url gets loaded into the context """ response = self._render_records() response_context_data = response.context_data self.assertIn("records_help_url", response_context_data) self.assertNotEqual(response_context_data["records_help_url"], "") @ddt.data( (Program.ACTIVE, True), (Program.RETIRED, True), (Program.DELETED, False), (Program.UNPUBLISHED, False), ) @ddt.unpack def test_completed_render_from_db(self, status, visible): """ Verify that a program cert that is completed is returned correctly, with different statuses """ self.program.status = status self.program.save() response = self.client.get(reverse("records:index")) self.assertEqual(response.status_code, 200) program_data = json.loads(response.context_data["programs"]) expected_program_data = [ { "name": self.program.title, "partner": "TestOrg1, TestOrg2", "uuid": self.program.uuid.hex, "type": slugify(self.program.type), "completed": True, "empty": False, } ] self.assertEqual(program_data, expected_program_data if visible else []) def test_in_progress_from_db(self): """ Verify that no program cert, but course certs results in an In Progress program """ # Delete the program self.program_cert.delete() response = self.client.get(reverse("records:index")) self.assertEqual(response.status_code, 200) program_data = json.loads(response.context_data["programs"]) expected_program_data = [ { "name": self.program.title, "partner": "TestOrg1, TestOrg2", "uuid": self.program.uuid.hex, "type": slugify(self.program.type), "completed": False, "empty": False, } ] self.assertEqual(program_data, expected_program_data) def test_not_visible_from_db(self): """ Test that the program's visible_date is considered """ UserCredentialAttributeFactory( user_credential=self.program_user_credential, name="visible_date", value="9999-01-01T01:01:01Z", ) response = self.client.get(reverse("records:index")) self.assertFalse(json.loads(response.context_data["programs"])[0]["completed"]) def test_multiple_programs(self): """ Test that multiple programs can appear, in progress and completed """ # Create a second program, and delete the first one's certificate new_course = CourseFactory.create(site=self.site) new_course_run = CourseRunFactory.create(course=new_course) new_program = ProgramFactory.create( title="ZTestProgram", course_runs=[new_course_run], authoring_organizations=self.orgs, site=self.site ) new_course_cert = CourseCertificateFactory.create(course_id=new_course_run.key, site=self.site) new_program_cert = ProgramCertificateFactory.create(program_uuid=new_program.uuid, site=self.site) # Make a new user credential UserCredentialFactory.create( username=self.user.username, credential_content_type=self.program_credential_content_type, credential=new_course_cert, ) # Make a new program credential UserCredentialFactory.create( username=self.user.username, credential_content_type=self.program_credential_content_type, credential=new_program_cert, ) self.program_user_credential.delete() response = self.client.get(reverse("records:index")) self.assertEqual(response.status_code, 200) program_data = json.loads(response.context_data["programs"]) expected_program_data = [ { "name": self.program.title, "partner": "TestOrg1, TestOrg2", "uuid": self.program.uuid.hex, "type": slugify(self.program.type), "completed": False, "empty": False, }, { "name": new_program.title, "partner": "TestOrg1, TestOrg2", "uuid": new_program.uuid.hex, "type": slugify(new_program.type), "completed": True, "empty": False, }, ] self.assertEqual(program_data, expected_program_data)
class ProgramCertificateIssuedEmailTests(SiteMixin, TestCase): """ Tests for the automated email sent to learners after completing an edX Program. """ USERNAME = "******" FAKE_PROGRAM_UUID = 'f6551af4-aa5a-4089-801b-53485d0d1726' def setUp(self): super().setUp() self.user = UserFactory(username=self.USERNAME) self.client.login(username=self.user.username, password=USER_PASSWORD) self.program = None self.program_cert = None mail.outbox = [] def _get_custom_completion_email_template_settings(self): return { 'f6551af4-aa5a-4089-801b-53485d0d1726': { 'plaintext': ''' I am email one I have the best content ''', 'html': ''' <p>I am email one</p> <p>I have the best content</p> ''', }, 'excellent-program': { 'plaintext': ''' I am email two I have better content ''', 'html': ''' <p>I am email two</p> <p>I have better content</p> ''', }, 'tubular-program': { 'plaintext': ''' I am email three I have great content too ''', 'html': ''' <p>I am email three</p> <p>I have great content too</p> ''', } } def _setup_program_and_program_cert(self, program_type, uuid=None): self.program = ProgramFactory(site=self.site) self.program.type = program_type self.program.type_slug = slugify(program_type) if uuid: self.program.uuid = uuid self.program.save() self.program_cert = ProgramCertificateFactory( site=self.site, program_uuid=self.program.uuid) def _build_expected_plaintext_email_body(self): custom_completion_email_template_settings = self._get_custom_completion_email_template_settings( ) email_fragments = [ 'Congratulations on completing the {} {} Program!'.format( self.program.title, self.program.type, ), 'Sincerely,', 'The {} Team'.format(self.site.siteconfiguration.platform_name), ] if custom_completion_email_template_settings.get(self.program.uuid): email_fragments.append( textwrap.dedent( custom_completion_email_template_settings.get( self.program.uuid).get('plaintext'))) elif custom_completion_email_template_settings.get( self.program.type_slug): email_fragments.append( textwrap.dedent( custom_completion_email_template_settings.get( self.program.type_slug).get('plaintext'))) return email_fragments def _build_expected_html_email_body(self): custom_completion_email_template_settings = self._get_custom_completion_email_template_settings( ) email_fragments = [ "Congratulations on completing the {} {} Program!".format( self.program.title, self.program.type), 'Sincerely,<br/>The {} Team'.format( self.site.siteconfiguration.platform_name), ] if custom_completion_email_template_settings.get(self.program.uuid): email_fragments.append( custom_completion_email_template_settings.get( self.program.uuid).get('html')) elif custom_completion_email_template_settings.get( self.program.type_slug): email_fragments.append( custom_completion_email_template_settings.get( self.program.type_slug).get('html')) return email_fragments def _assert_email_contents(self): """ Utility function that verifies the contents of the generated email """ expected_plaintext_email_contents = self._build_expected_plaintext_email_body( ) expected_html_email_contents = self._build_expected_html_email_body() self.assertEqual(1, len(mail.outbox)) email = mail.outbox[0] plaintext_body = email.body html_body = email.alternatives[0][0] self.assertEqual(email.to[0], self.user.email) self._assert_subject(email.subject) self._assert_email_body_contents(plaintext_body, expected_plaintext_email_contents) self._assert_email_body_contents(html_body, expected_html_email_contents) def _assert_subject(self, email_subject): """ Utility method that verifies the subject text of the automated emails being sent to learners. """ expected_subject = 'Congratulations for finishing your {} {} Program!'.format( self.program.title, self.program.type) self.assertEqual(email_subject, expected_subject) def _assert_email_body_contents(self, email_body, fragments): """ Utility method that verifies the content in the generated email is as expected. """ for fragment in fragments: self.assertIn(fragment, email_body) def test_base_template(self): self._setup_program_and_program_cert('Radical Program') send_program_certificate_created_message(self.user.username, self.program_cert) self._assert_email_contents() def test_custom_email_template_program_uuid(self): """ Test for the contents of a custom email template. """ self._setup_program_and_program_cert('Excellent Program', self.FAKE_PROGRAM_UUID) with self.settings(CUSTOM_COMPLETION_EMAIL_TEMPLATE_EXTRA=self. _get_custom_completion_email_template_settings()): send_program_certificate_created_message(self.user.username, self.program_cert) self._assert_email_contents() def test_custom_email_template_program_type(self): self._setup_program_and_program_cert('Excellent Program') with self.settings(CUSTOM_COMPLETION_EMAIL_TEMPLATE_EXTRA=self. _get_custom_completion_email_template_settings()): send_program_certificate_created_message(self.user.username, self.program_cert) self._assert_email_contents() def test_custom_email_template_alternative_program_type(self): self._setup_program_and_program_cert('Tubular Program') with self.settings(CUSTOM_COMPLETION_EMAIL_TEMPLATE_EXTRA=self. _get_custom_completion_email_template_settings()): send_program_certificate_created_message(self.user.username, self.program_cert) self._assert_email_contents() def test_send_email_exception_occurs(self): self._setup_program_and_program_cert("Radical Program") expected_messages = [ 'Sending Program completion email to learner with id [{}] in Program [{}]' .format(self.user.id, self.program.uuid), 'Unable to send email to learner with id: [{}] for Program [{}]. Error occurred while attempting to ' 'format or send message: Error!'.format(self.user.id, self.program.uuid) ] with LogCapture() as log: with mock.patch('edx_ace.ace.send', side_effect=Exception("Error!")): send_program_certificate_created_message( self.user.username, self.program_cert) for index, message in enumerate(expected_messages): assert message in log.records[index].getMessage()
class ProgramListingViewTests(SiteMixin, TestCase): MOCK_USER_DATA = { "username": "******", "name": "Test User", "email": "*****@*****.**", } def setUp(self): super().setUp() dump_random_state() self.user = UserFactory(username=self.MOCK_USER_DATA["username"], is_staff=True) self.orgs = [OrganizationFactory.create(name=name, site=self.site) for name in ["TestOrg1", "TestOrg2"]] self.course = CourseFactory.create(site=self.site) self.course_runs = CourseRunFactory.create_batch(2, course=self.course) self.program = ProgramFactory( title="TestProgram1", course_runs=self.course_runs, authoring_organizations=self.orgs, site=self.site ) self.course_certs = [ CourseCertificateFactory.create( course_id=course_run.key, site=self.site, ) for course_run in self.course_runs ] self.program_cert = ProgramCertificateFactory.create(program_uuid=self.program.uuid, site=self.site) self.course_credential_content_type = ContentType.objects.get( app_label="credentials", model="coursecertificate" ) self.program_credential_content_type = ContentType.objects.get( app_label="credentials", model="programcertificate" ) self.course_user_credentials = [ UserCredentialFactory.create( username=self.user.username, credential_content_type=self.course_credential_content_type, credential=course_cert, ) for course_cert in self.course_certs ] self.program_user_credentials = UserCredentialFactory.create( username=self.user.username, credential_content_type=self.program_credential_content_type, credential=self.program_cert, ) self.client.login(username=self.user.username, password=USER_PASSWORD) def _render_listing(self, expected_program_data=None, status_code=200): """ Helper method to mock and render a user certificate.""" response = self.client.get(reverse("program_listing")) self.assertEqual(response.status_code, status_code) if expected_program_data is not None: program_data = json.loads(response.context_data["programs"]) self.assertListEqual(program_data, expected_program_data) return response def _default_program_data(self, overrides=None): # if nothing is adjusted, this is the expected listing data = [ { "name": self.program.title, "partner": "TestOrg1, TestOrg2", "uuid": self.program.uuid.hex, "type": slugify(self.program.type), "completed": True, "empty": False, }, ] if overrides is not None: data[0].update(overrides) return data def _verify_normal_access(self): response = self._render_listing() response_context_data = response.context_data self.assertContains(response, "Program Listing View") actual_child_templates = response_context_data["child_templates"] self.assert_matching_template_origin(actual_child_templates["footer"], "_footer.html") self.assert_matching_template_origin(actual_child_templates["header"], "_header.html") self.assertNotIn("masquerade", actual_child_templates) # no masquerading on this view def assert_matching_template_origin(self, actual, expected_template_name): expected = select_template([expected_template_name]) self.assertEqual(actual.origin, expected.origin) def test_no_anonymous_access(self): """ Verify that the view rejects non-logged-in users. """ self.client.logout() response = self._render_listing(status_code=302) self.assertRegex(response.url, "^/login/.*") def test_non_superuser_access(self): """ Verify that the view rejects non-superuser users. """ self.user.is_superuser = False self.user.is_staff = False self.user.save() self._render_listing(status_code=404) def test_only_staff_access(self): """ Verify that the view rejects non-staff users. """ self.user.is_staff = False self.user.save() self._render_listing(status_code=404) def test_normal_access_superuser(self): """ Verify that the view works with only superuser, no staff. """ self.user.is_superuser = True self.user.is_staff = False self._verify_normal_access() def test_normal_access_as_staff(self): """ Verify that the view works in default case. Staff is set in the setup method.""" self._verify_normal_access() @ddt.data( (Program.ACTIVE, True), (Program.RETIRED, False), # this is different from RecordsView (Program.DELETED, False), (Program.UNPUBLISHED, False), ) @ddt.unpack def test_completed_render_from_db(self, status, visible): """ Verify that a program cert that is completed is returned correctly, with different statuses """ self.program.status = status self.program.save() data = self._default_program_data() if visible else [] self._render_listing(expected_program_data=data) def test_in_progress_from_db(self): """ Verify that no program cert, but course certs results in an In Progress program """ # Delete the program cert self.program_cert.delete() data = self._default_program_data(overrides={"completed": False}) self._render_listing(expected_program_data=data) def test_empty_programs(self): """ Test that a program with no certs shows as empty """ # Delete all certs for cert in self.course_certs: cert.delete() self.program_cert.delete() data = self._default_program_data(overrides={"completed": False, "empty": True}) self._render_listing(expected_program_data=data)
class RecordsViewTests(SiteMixin, TestCase): MOCK_USER_DATA = {'username': '******', 'name': 'Test User', 'email': '*****@*****.**', } def setUp(self): super().setUp() dump_random_state() self.user = UserFactory(username=self.MOCK_USER_DATA['username']) self.orgs = [OrganizationFactory.create(name=name, site=self.site) for name in ['TestOrg1', 'TestOrg2']] self.course = CourseFactory.create(site=self.site) self.course_runs = CourseRunFactory.create_batch(2, course=self.course) self.program = ProgramFactory(title="TestProgram1", course_runs=self.course_runs, authoring_organizations=self.orgs, site=self.site) self.course_certs = [CourseCertificateFactory.create( course_id=course_run.key, site=self.site, ) for course_run in self.course_runs] self.program_cert = ProgramCertificateFactory.create(program_uuid=self.program.uuid, site=self.site) self.course_credential_content_type = ContentType.objects.get( app_label='credentials', model='coursecertificate' ) self.program_credential_content_type = ContentType.objects.get( app_label='credentials', model='programcertificate' ) self.course_user_credentials = [UserCredentialFactory.create( username=self.user.username, credential_content_type=self.course_credential_content_type, credential=course_cert ) for course_cert in self.course_certs] self.program_user_credential = UserCredentialFactory.create( username=self.user.username, credential_content_type=self.program_credential_content_type, credential=self.program_cert ) self.client.login(username=self.user.username, password=USER_PASSWORD) def _render_records(self, program_data=None, status_code=200): """ Helper method to mock and render a user certificate.""" if program_data is None: program_data = [] with patch('credentials.apps.records.views.RecordsView._get_programs') as get_programs: get_programs.return_value = program_data response = self.client.get(reverse('records:index')) self.assertEqual(response.status_code, status_code) return response def assert_matching_template_origin(self, actual, expected_template_name): expected = select_template([expected_template_name]) self.assertEqual(actual.origin, expected.origin) def test_no_anonymous_access(self): """ Verify that the view rejects non-logged-in users. """ self.client.logout() response = self._render_records(status_code=302) self.assertRegex(response.url, '^/login/.*') # pylint: disable=deprecated-method def test_normal_access(self): """ Verify that the view works in default case. """ response = self._render_records() response_context_data = response.context_data self.assertContains(response, 'My Learner Records') actual_child_templates = response_context_data['child_templates'] self.assert_matching_template_origin(actual_child_templates['footer'], '_footer.html') self.assert_matching_template_origin(actual_child_templates['header'], '_header.html') self.assert_matching_template_origin(actual_child_templates['masquerade'], '_masquerade.html') def test_xss(self): """ Verify that the view protects against xss in translations. """ response = self._render_records([ { "name": "<xss>", 'partner': 'XSS', 'uuid': 'uuid', }, ]) # Test that the data is parsed from an escaped string self.assertContains(response, 'JSON.parse(\'[{' + '\\u0022name\\u0022: \\u0022\\u003Cxss\\u003E\\u0022, ' + '\\u0022partner\\u0022: \\u0022XSS\\u0022, ' + '\\u0022uuid\\u0022: \\u0022uuid\\u0022' + '}]\')') self.assertNotContains(response, '<xss>') def test_help_url(self): """ Verify that the records help url gets loaded into the context """ response = self._render_records() response_context_data = response.context_data self.assertIn('records_help_url', response_context_data) self.assertNotEqual(response_context_data['records_help_url'], '') @ddt.data( (Program.ACTIVE, True), (Program.RETIRED, True), (Program.DELETED, False), (Program.UNPUBLISHED, False), ) @ddt.unpack def test_completed_render_from_db(self, status, visible): """ Verify that a program cert that is completed is returned correctly, with different statuses """ self.program.status = status self.program.save() response = self.client.get(reverse('records:index')) self.assertEqual(response.status_code, 200) program_data = json.loads(response.context_data['programs']) expected_program_data = [ { 'name': self.program.title, 'partner': 'TestOrg1, TestOrg2', 'uuid': self.program.uuid.hex, 'type': slugify(self.program.type), 'completed': True, 'empty': False, } ] self.assertEqual(program_data, expected_program_data if visible else []) def test_in_progress_from_db(self): """ Verify that no program cert, but course certs reuslts in an In Progress program """ # Delete the program self.program_cert.delete() response = self.client.get(reverse('records:index')) self.assertEqual(response.status_code, 200) program_data = json.loads(response.context_data['programs']) expected_program_data = [ { 'name': self.program.title, 'partner': 'TestOrg1, TestOrg2', 'uuid': self.program.uuid.hex, 'type': slugify(self.program.type), 'completed': False, 'empty': False, } ] self.assertEqual(program_data, expected_program_data) def test_not_visible_from_db(self): """ Test that the program's visible_date is considered """ UserCredentialAttributeFactory( user_credential=self.program_user_credential, name='visible_date', value='9999-01-01T01:01:01Z', ) response = self.client.get(reverse('records:index')) self.assertFalse(json.loads(response.context_data['programs'])[0]['completed']) def test_multiple_programs(self): """ Test that multiple programs can appear, in progress and completed """ # Create a second program, and delete the first one's certificate new_course = CourseFactory.create(site=self.site) new_course_run = CourseRunFactory.create(course=new_course) new_program = ProgramFactory.create(title='ZTestProgram', course_runs=[new_course_run], authoring_organizations=self.orgs, site=self.site) new_course_cert = CourseCertificateFactory.create(course_id=new_course_run.key, site=self.site) new_program_cert = ProgramCertificateFactory.create(program_uuid=new_program.uuid, site=self.site) # Make a new user credential UserCredentialFactory.create( username=self.user.username, credential_content_type=self.program_credential_content_type, credential=new_course_cert ) # Make a new program credential UserCredentialFactory.create( username=self.user.username, credential_content_type=self.program_credential_content_type, credential=new_program_cert ) self.program_user_credential.delete() response = self.client.get(reverse('records:index')) self.assertEqual(response.status_code, 200) program_data = json.loads(response.context_data['programs']) expected_program_data = [ { 'name': self.program.title, 'partner': 'TestOrg1, TestOrg2', 'uuid': self.program.uuid.hex, 'type': slugify(self.program.type), 'completed': False, 'empty': False, }, { 'name': new_program.title, 'partner': 'TestOrg1, TestOrg2', 'uuid': new_program.uuid.hex, 'type': slugify(new_program.type), 'completed': True, 'empty': False, } ] self.assertEqual(program_data, expected_program_data)
class ProgramListingViewTests(SiteMixin, TestCase): MOCK_USER_DATA = {'username': '******', 'name': 'Test User', 'email': '*****@*****.**', } def setUp(self): super().setUp() dump_random_state() self.user = UserFactory(username=self.MOCK_USER_DATA['username'], is_superuser=True) self.orgs = [OrganizationFactory.create(name=name, site=self.site) for name in ['TestOrg1', 'TestOrg2']] self.course = CourseFactory.create(site=self.site) self.course_runs = CourseRunFactory.create_batch(2, course=self.course) self.program = ProgramFactory(title="TestProgram1", course_runs=self.course_runs, authoring_organizations=self.orgs, site=self.site) self.course_certs = [CourseCertificateFactory.create( course_id=course_run.key, site=self.site, ) for course_run in self.course_runs] self.program_cert = ProgramCertificateFactory.create(program_uuid=self.program.uuid, site=self.site) self.course_credential_content_type = ContentType.objects.get( app_label='credentials', model='coursecertificate' ) self.program_credential_content_type = ContentType.objects.get( app_label='credentials', model='programcertificate' ) self.course_user_credentials = [UserCredentialFactory.create( username=self.user.username, credential_content_type=self.course_credential_content_type, credential=course_cert ) for course_cert in self.course_certs] self.program_user_credentials = UserCredentialFactory.create( username=self.user.username, credential_content_type=self.program_credential_content_type, credential=self.program_cert ) self.client.login(username=self.user.username, password=USER_PASSWORD) def _render_listing(self, expected_program_data=None, status_code=200): """ Helper method to mock and render a user certificate.""" response = self.client.get(reverse('program_listing')) self.assertEqual(response.status_code, status_code) if expected_program_data is not None: program_data = json.loads(response.context_data['programs']) self.assertListEqual(program_data, expected_program_data) return response def _default_program_data(self, overrides=None): # if nothing is adjusted, this is the expected listing data = [ { 'name': self.program.title, 'partner': 'TestOrg1, TestOrg2', 'uuid': self.program.uuid.hex, 'type': slugify(self.program.type), 'completed': True, 'empty': False, }, ] if overrides is not None: data[0].update(overrides) return data def assert_matching_template_origin(self, actual, expected_template_name): expected = select_template([expected_template_name]) self.assertEqual(actual.origin, expected.origin) def test_no_anonymous_access(self): """ Verify that the view rejects non-logged-in users. """ self.client.logout() response = self._render_listing(status_code=302) self.assertRegexpMatches(response.url, '^/login/.*') # pylint: disable=deprecated-method def test_only_superuser_access(self): """ Verify that the view rejects non-superusers. """ self.user.is_superuser = False self.user.save() self._render_listing(status_code=404) def test_normal_access(self): """ Verify that the view works in default case. """ response = self._render_listing() response_context_data = response.context_data self.assertContains(response, 'Program Listing View') actual_child_templates = response_context_data['child_templates'] self.assert_matching_template_origin(actual_child_templates['footer'], '_footer.html') self.assert_matching_template_origin(actual_child_templates['header'], '_header.html') self.assertFalse('masquerade' in actual_child_templates) # no masquerading on this view @ddt.data( (Program.ACTIVE, True), (Program.RETIRED, False), # this is different from RecordsView (Program.DELETED, False), (Program.UNPUBLISHED, False), ) @ddt.unpack def test_completed_render_from_db(self, status, visible): """ Verify that a program cert that is completed is returned correctly, with different statuses """ self.program.status = status self.program.save() data = self._default_program_data() if visible else [] self._render_listing(expected_program_data=data) def test_in_progress_from_db(self): """ Verify that no program cert, but course certs results in an In Progress program """ # Delete the program cert self.program_cert.delete() data = self._default_program_data(overrides={'completed': False}) self._render_listing(expected_program_data=data) def test_empty_programs(self): """ Test that a program with no certs shows as empty """ # Delete all certs for cert in self.course_certs: cert.delete() self.program_cert.delete() data = self._default_program_data(overrides={'completed': False, 'empty': True}) self._render_listing(expected_program_data=data)
class ProgramCertificateIssuedEmailTests(SiteMixin, TestCase): """ Tests for the automated email sent to learners after completing an edX Program. Testing that the right configuration is used is done in `test_models.py` so this code will only verify that an email matching the configuration is sent. """ USERNAME = "******" FAKE_PROGRAM_UUID = "f6551af4-aa5a-4089-801b-53485d0d1726" def setUp(self): super().setUp() self.user = UserFactory(username=self.USERNAME) self.client.login(username=self.user.username, password=USER_PASSWORD) self.program = None self.program_cert = None mail.outbox = [] # Setup program and default email config self._setup_program_and_program_cert("Example Program") self.default_config = ProgramCompletionEmailConfiguration.objects.create( identifier="default", html_template="<h1>Default Template</h1>", plaintext_template="Default Template", enabled=True, ) def _setup_program_and_program_cert(self, program_type): self.program = ProgramFactory(site=self.site) self.program.type = program_type self.program.type_slug = slugify(program_type) self.program.save() self.program_cert = ProgramCertificateFactory( site=self.site, program_uuid=self.program.uuid) def _build_expected_plaintext_email_body(self): return [ "Congratulations on completing the {} {} Program!".format( self.program.title, self.program.type, ), "Sincerely,", "The {} Team".format(self.site.siteconfiguration.platform_name), textwrap.dedent(self.default_config.plaintext_template), ] def _build_expected_html_email_body(self): return [ "Congratulations on completing the {} {} Program!".format( self.program.title, self.program.type), "Sincerely,<br/>The {} Team".format( self.site.siteconfiguration.platform_name), self.default_config.html_template, ] def _assert_email_contents(self): """ Utility function that verifies the contents of the generated email """ expected_plaintext_email_contents = self._build_expected_plaintext_email_body( ) expected_html_email_contents = self._build_expected_html_email_body() self.assertEqual(1, len(mail.outbox)) email = mail.outbox[0] plaintext_body = email.body html_body = email.alternatives[0][0] self.assertEqual(email.to[0], self.user.email) self._assert_subject(email.subject) self._assert_email_body_contents(plaintext_body, expected_plaintext_email_contents) self._assert_email_body_contents(html_body, expected_html_email_contents) def _assert_subject(self, email_subject): """ Utility method that verifies the subject text of the automated emails being sent to learners. """ expected_subject = "Congratulations for finishing your {} {} Program!".format( self.program.title, self.program.type) self.assertEqual(email_subject, expected_subject) def _assert_email_body_contents(self, email_body, fragments): """ Utility method that verifies the content in the generated email is as expected. """ for fragment in fragments: self.assertIn(fragment, email_body) def test_base_template(self): send_program_certificate_created_message(self.user.username, self.program_cert) self._assert_email_contents() def test_no_config(self): """With the config deleted, it shouldn't send an email""" self.default_config.delete() send_program_certificate_created_message(self.user.username, self.program_cert) self.assertEqual(0, len(mail.outbox)) def test_disabled_config(self): """With the config disabled, it shouldn't send an email""" self.default_config.enabled = False self.default_config.save() send_program_certificate_created_message(self.user.username, self.program_cert) self.assertEqual(0, len(mail.outbox)) def test_retired_program(self): self.program.status = ProgramStatus.RETIRED.value self.program.save() send_program_certificate_created_message(self.user.username, self.program_cert) self.assertEqual(0, len(mail.outbox)) def test_send_email_exception_occurs(self): self._setup_program_and_program_cert("Radical Program") expected_messages = [ "Sending Program completion email to learner with id [{}] in Program [{}]" .format(self.user.id, self.program.uuid), "Unable to send email to learner with id: [{}] for Program [{}]. Error occurred while attempting to " "format or send message: Error!".format(self.user.id, self.program.uuid), ] with LogCapture() as log: with mock.patch("edx_ace.ace.send", side_effect=Exception("Error!")): send_program_certificate_created_message( self.user.username, self.program_cert) for index, message in enumerate(expected_messages): assert message in log.records[index].getMessage()