From 1ffa81f74684cfecfd7ffe95c1789efe7681bf45 Mon Sep 17 00:00:00 2001 From: Renzo Lucioni Date: Wed, 20 Apr 2016 15:59:35 -0400 Subject: [PATCH] Retrofit programs data mixin with factories This should make it easier to test programs-related features. Preface to ECOM-3200. --- common/test/acceptance/fixtures/programs.py | 61 +---- .../learner_dashboard/tests/test_programs.py | 57 +++-- .../djangoapps/programs/tests/factories.py | 54 +++++ .../core/djangoapps/programs/tests/mixins.py | 209 ++++-------------- 4 files changed, 127 insertions(+), 254 deletions(-) create mode 100644 openedx/core/djangoapps/programs/tests/factories.py diff --git a/common/test/acceptance/fixtures/programs.py b/common/test/acceptance/fixtures/programs.py index 72ac918248..6a26745bd5 100644 --- a/common/test/acceptance/fixtures/programs.py +++ b/common/test/acceptance/fixtures/programs.py @@ -4,67 +4,16 @@ Tools to create programs-related data for use in bok choy tests. from collections import namedtuple import json -import factory import requests from . import PROGRAMS_STUB_URL from .config import ConfigModelFixture +from openedx.core.djangoapps.programs.tests import factories FakeProgram = namedtuple('FakeProgram', ['name', 'status', 'org_key', 'course_id']) -class Program(factory.Factory): - """ - Factory for stubbing program resources from the Programs API (v1). - """ - class Meta(object): - model = dict - - id = factory.Sequence(lambda n: n) # pylint: disable=invalid-name - name = 'dummy-program-name' - subtitle = 'dummy-program-subtitle' - category = 'xseries' - status = 'unpublished' - marketing_slug = factory.Sequence(lambda n: 'slug-{}'.format(n)) # pylint: disable=unnecessary-lambda - organizations = [] - course_codes = [] - banner_image_urls = {} - - -class Organization(factory.Factory): - """ - Factory for stubbing nested organization resources from the Programs API (v1). - """ - class Meta(object): - model = dict - - key = 'dummyX' - display_name = 'dummy-org-display-name' - - -class CourseCode(factory.Factory): - """ - Factory for stubbing nested course code resources from the Programs API (v1). - """ - class Meta(object): - model = dict - - display_name = 'dummy-org-display-name' - run_modes = [] - - -class RunMode(factory.Factory): - """ - Factory for stubbing nested run mode resources from the Programs API (v1). - """ - class Meta(object): - model = dict - - course_key = 'org/course/run' - mode_slug = 'verified' - - class ProgramsFixture(object): """ Interface to set up mock responses from the Programs stub server. @@ -78,11 +27,11 @@ class ProgramsFixture(object): """ programs = [] for program in fake_programs: - run_mode = RunMode(course_key=program.course_id) - course_code = CourseCode(run_modes=[run_mode]) - org = Organization(key=program.org_key) + run_mode = factories.RunMode(course_key=program.course_id) + course_code = factories.CourseCode(run_modes=[run_mode]) + org = factories.Organization(key=program.org_key) - program = Program( + program = factories.Program( name=program.name, status=program.status, organizations=[org], diff --git a/lms/djangoapps/learner_dashboard/tests/test_programs.py b/lms/djangoapps/learner_dashboard/tests/test_programs.py index 42271d5f8e..a9cc3a25ac 100644 --- a/lms/djangoapps/learner_dashboard/tests/test_programs.py +++ b/lms/djangoapps/learner_dashboard/tests/test_programs.py @@ -8,7 +8,6 @@ from urlparse import urljoin from django.conf import settings from django.core.urlresolvers import reverse -from django.http import HttpResponseRedirect from django.test import override_settings from edx_oauth2_provider.tests.factories import ClientFactory from opaque_keys.edx import locator @@ -39,6 +38,7 @@ class TestProgramListing( Unit tests for getting the list of programs enrolled by a logged in user """ PASSWORD = 'test' + url = reverse('program_listing_view') def setUp(self): """ @@ -81,10 +81,9 @@ class TestProgramListing( """ self.mock_programs_api() self.client.login(username=self.student.username, password=self.PASSWORD) - response = self.client.get(reverse("program_listing_view")) - self.assertEqual(response.status_code, 200) + response = self.client.get(self.url) x_series_url = urljoin(settings.MKTG_URLS.get('ROOT'), 'xseries') - self.assertIn(x_series_url, response.content) + self.assertContains(response, x_series_url) return response def _get_program_checklist(self, program_id): @@ -101,18 +100,18 @@ class TestProgramListing( def test_get_program_with_no_enrollment(self): response = self._setup_and_get_program() for program_element in self._get_program_checklist(0): - self.assertNotIn(program_element, response.content) + self.assertNotContains(response, program_element) for program_element in self._get_program_checklist(1): - self.assertNotIn(program_element, response.content) + self.assertNotContains(response, program_element) @httpretty.activate def test_get_one_program(self): self._create_course_and_enroll(self.student, *self.COURSE_KEYS[0].split('/')) response = self._setup_and_get_program() for program_element in self._get_program_checklist(0): - self.assertIn(program_element, response.content) + self.assertContains(response, program_element) for program_element in self._get_program_checklist(1): - self.assertNotIn(program_element, response.content) + self.assertNotContains(response, program_element) @httpretty.activate def test_get_both_program(self): @@ -120,32 +119,34 @@ class TestProgramListing( self._create_course_and_enroll(self.student, *self.COURSE_KEYS[5].split('/')) response = self._setup_and_get_program() for program_element in self._get_program_checklist(0): - self.assertIn(program_element, response.content) + self.assertContains(response, program_element) for program_element in self._get_program_checklist(1): - self.assertIn(program_element, response.content) + self.assertContains(response, program_element) def test_get_programs_dashboard_not_enabled(self): self.create_programs_config(program_listing_enabled=False) self.client.login(username=self.student.username, password=self.PASSWORD) - response = self.client.get(reverse("program_listing_view")) + response = self.client.get(self.url) self.assertEqual(response.status_code, 404) def test_xseries_advertise_disabled(self): self.create_programs_config(program_listing_enabled=True, xseries_ad_enabled=False) self.client.login(username=self.student.username, password=self.PASSWORD) - response = self.client.get(reverse("program_listing_view")) - self.assertEqual(response.status_code, 200) + response = self.client.get(self.url) x_series_url = urljoin(settings.MKTG_URLS.get('ROOT'), 'xseries') - self.assertNotIn(x_series_url, response.content) + self.assertNotContains(response, x_series_url) def test_get_programs_not_logged_in(self): self.create_programs_config() - response = self.client.get(reverse("program_listing_view")) - self.assertEqual(response.status_code, 302) - self.assertIsInstance(response, HttpResponseRedirect) - self.assertIn('login', response.url) # pylint: disable=no-member + response = self.client.get(self.url) - def _expected_credetials_data(self): + self.assertRedirects( + response, + '{}?next={}'.format(reverse('signin_user'), self.url) + ) + + # TODO: Use a factory to generate this data. + def _expected_credentials_data(self): """ Dry method for getting expected credentials.""" return [ @@ -172,13 +173,11 @@ class TestProgramListing( self.mock_credentials_api(self.student, data=self.CREDENTIALS_API_RESPONSE, reset_url=False) response = self.client.get(reverse("program_listing_view")) - self.assertEqual(response.status_code, 200) + for certificate in self._expected_credentials_data(): + self.assertContains(response, certificate['display_name']) + self.assertContains(response, certificate['credential_url']) - for certificate in self._expected_credetials_data(): - self.assertIn(certificate['display_name'], response.content) - self.assertIn(certificate['credential_url'], response.content) - - self.assertIn('images/xseries-certificate-visual.png', response.content) + self.assertContains(response, 'images/xseries-certificate-visual.png') @httpretty.activate def test_get_xseries_certificates_without_data(self): @@ -193,8 +192,6 @@ class TestProgramListing( self.mock_credentials_api(self.student, data={"results": []}, reset_url=False) response = self.client.get(reverse("program_listing_view")) - self.assertEqual(response.status_code, 200) - - for certificate in self._expected_credetials_data(): - self.assertNotIn(certificate['display_name'], response.content) - self.assertNotIn(certificate['credential_url'], response.content) + for certificate in self._expected_credentials_data(): + self.assertNotContains(response, certificate['display_name']) + self.assertNotContains(response, certificate['credential_url']) diff --git a/openedx/core/djangoapps/programs/tests/factories.py b/openedx/core/djangoapps/programs/tests/factories.py new file mode 100644 index 0000000000..10b97de2f3 --- /dev/null +++ b/openedx/core/djangoapps/programs/tests/factories.py @@ -0,0 +1,54 @@ +"""Factories for generating fake program-related data.""" +import factory +from factory.fuzzy import FuzzyText + + +class Program(factory.Factory): + """ + Factory for stubbing program resources from the Programs API (v1). + """ + class Meta(object): + model = dict + + id = factory.Sequence(lambda n: n) # pylint: disable=invalid-name + name = FuzzyText(prefix='Program ') + subtitle = FuzzyText(prefix='Subtitle ') + category = 'xseries' + status = 'unpublished' + marketing_slug = FuzzyText(prefix='slug_') + organizations = [] + course_codes = [] + banner_image_urls = {} + + +class Organization(factory.Factory): + """ + Factory for stubbing nested organization resources from the Programs API (v1). + """ + class Meta(object): + model = dict + + key = FuzzyText(prefix='org_') + display_name = FuzzyText(prefix='Display Name ') + + +class CourseCode(factory.Factory): + """ + Factory for stubbing nested course code resources from the Programs API (v1). + """ + class Meta(object): + model = dict + + display_name = FuzzyText(prefix='Display Name ') + run_modes = [] + + +class RunMode(factory.Factory): + """ + Factory for stubbing nested run mode resources from the Programs API (v1). + """ + class Meta(object): + model = dict + + course_key = FuzzyText(prefix='org/', suffix='/run') + mode_slug = 'verified' diff --git a/openedx/core/djangoapps/programs/tests/mixins.py b/openedx/core/djangoapps/programs/tests/mixins.py index 3c1f710c95..8e62886f89 100644 --- a/openedx/core/djangoapps/programs/tests/mixins.py +++ b/openedx/core/djangoapps/programs/tests/mixins.py @@ -4,6 +4,7 @@ import json import httpretty from openedx.core.djangoapps.programs.models import ProgramsApiConfig +from openedx.core.djangoapps.programs.tests import factories class ProgramsApiConfigMixin(object): @@ -50,176 +51,48 @@ class ProgramsDataMixin(object): 'organization-b/course-d/winter', ] - # TODO: Use factory-boy. PROGRAMS_API_RESPONSE = { 'results': [ - { - 'id': 1, - 'name': PROGRAM_NAMES[0], - 'subtitle': 'A program used for testing purposes', - 'category': 'xseries', - 'status': 'unpublished', - 'marketing_slug': '{}_test_url'.format(PROGRAM_NAMES[0].replace(' ', '_')), - 'organizations': [ - { - 'display_name': 'Test Organization A', - 'key': 'organization-a' - } - ], - 'course_codes': [ - { - 'display_name': 'Test Course A', - 'key': 'course-a', - 'organization': { - 'display_name': 'Test Organization A', - 'key': 'organization-a' - }, - 'run_modes': [ - { - 'course_key': COURSE_KEYS[0], - 'mode_slug': 'verified', - 'sku': '', - 'start_date': '2015-11-05T07:39:02.791741Z', - 'run_key': 'fall' - }, - { - 'course_key': COURSE_KEYS[1], - 'mode_slug': 'verified', - 'sku': '', - 'start_date': '2015-11-05T07:39:02.791741Z', - 'run_key': 'winter' - } - ] - }, - { - 'display_name': 'Test Course B', - 'key': 'course-b', - 'organization': { - 'display_name': 'Test Organization A', - 'key': 'organization-a' - }, - 'run_modes': [ - { - 'course_key': COURSE_KEYS[2], - 'mode_slug': 'verified', - 'sku': '', - 'start_date': '2015-11-05T07:39:02.791741Z', - 'run_key': 'fall' - }, - { - 'course_key': COURSE_KEYS[3], - 'mode_slug': 'verified', - 'sku': '', - 'start_date': '2015-11-05T07:39:02.791741Z', - 'run_key': 'winter' - } - ] - } - ], - 'created': '2015-10-26T17:52:32.861000Z', - 'modified': '2015-11-18T22:21:30.826365Z' - }, - { - 'id': 2, - 'name': PROGRAM_NAMES[1], - 'subtitle': 'Another program used for testing purposes', - 'category': 'xseries', - 'status': 'unpublished', - 'marketing_slug': '{}_test_url'.format(PROGRAM_NAMES[1].replace(' ', '_')), - 'organizations': [ - { - 'display_name': 'Test Organization B', - 'key': 'organization-b' - } - ], - 'course_codes': [ - { - 'display_name': 'Test Course C', - 'key': 'course-c', - 'organization': { - 'display_name': 'Test Organization B', - 'key': 'organization-b' - }, - 'run_modes': [ - { - 'course_key': COURSE_KEYS[4], - 'mode_slug': 'verified', - 'sku': '', - 'start_date': '2015-11-05T07:39:02.791741Z', - 'run_key': 'fall' - }, - { - 'course_key': COURSE_KEYS[5], - 'mode_slug': 'verified', - 'sku': '', - 'start_date': '2015-11-05T07:39:02.791741Z', - 'run_key': 'winter' - } - ] - }, - { - 'display_name': 'Test Course D', - 'key': 'course-d', - 'organization': { - 'display_name': 'Test Organization B', - 'key': 'organization-b' - }, - 'run_modes': [ - { - 'course_key': COURSE_KEYS[6], - 'mode_slug': 'verified', - 'sku': '', - 'start_date': '2015-11-05T07:39:02.791741Z', - 'run_key': 'fall' - }, - { - 'course_key': COURSE_KEYS[7], - 'mode_slug': 'verified', - 'sku': '', - 'start_date': '2015-11-05T07:39:02.791741Z', - 'run_key': 'winter' - } - ] - } - ], - 'created': '2015-10-26T19:59:03.064000Z', - 'modified': '2015-10-26T19:59:18.536000Z' - }, - { - 'id': 3, - 'name': PROGRAM_NAMES[2], - 'subtitle': 'A third program used for testing purposes', - 'category': 'xseries', - 'status': 'unpublished', - 'marketing_slug': '{}_test_url'.format(PROGRAM_NAMES[2].replace(' ', '_')), - 'organizations': [ - { - 'display_name': 'Test Organization B', - 'key': 'organization-b' - } - ], - 'course_codes': [ - { - 'display_name': 'Test Course D', - 'key': 'course-d', - 'organization': { - 'display_name': 'Test Organization B', - 'key': 'organization-b' - }, - 'run_modes': [ - { - 'course_key': COURSE_KEYS[7], - 'mode_slug': 'verified', - 'sku': '', - 'start_date': '2015-11-05T07:39:02.791741Z', - 'run_key': 'winter' - } - ] - } - ], - 'created': '2015-10-26T19:59:03.064000Z', - 'modified': '2015-10-26T19:59:18.536000Z' - } + factories.Program( + id=1, + name=PROGRAM_NAMES[0], + organizations=[factories.Organization()], + course_codes=[ + factories.CourseCode(run_modes=[ + factories.RunMode(course_key=COURSE_KEYS[0]), + factories.RunMode(course_key=COURSE_KEYS[1]), + ]), + factories.CourseCode(run_modes=[ + factories.RunMode(course_key=COURSE_KEYS[2]), + factories.RunMode(course_key=COURSE_KEYS[3]), + ]), + ] + ), + factories.Program( + id=2, + name=PROGRAM_NAMES[1], + organizations=[factories.Organization()], + course_codes=[ + factories.CourseCode(run_modes=[ + factories.RunMode(course_key=COURSE_KEYS[4]), + factories.RunMode(course_key=COURSE_KEYS[5]), + ]), + factories.CourseCode(run_modes=[ + factories.RunMode(course_key=COURSE_KEYS[6]), + factories.RunMode(course_key=COURSE_KEYS[7]), + ]), + ] + ), + factories.Program( + id=3, + name=PROGRAM_NAMES[2], + organizations=[factories.Organization()], + course_codes=[ + factories.CourseCode(run_modes=[ + factories.RunMode(course_key=COURSE_KEYS[7]), + ]), + ] + ), ] }