diff --git a/cms/static/js/programs/models/program_model.js b/cms/static/js/programs/models/program_model.js index 1677633..2a38c4e 100644 --- a/cms/static/js/programs/models/program_model.js +++ b/cms/static/js/programs/models/program_model.js @@ -24,7 +24,8 @@ define([ }, category: { required: true, - oneOf: ['xseries', 'micromasters'] + // TODO: Populate with the results of an API call for valid categories. + oneOf: ['XSeries', 'MicroMasters'] }, organizations: 'validateOrganizations', marketing_slug: { diff --git a/cms/static/js/spec/views/programs/program_creator_spec.js b/cms/static/js/spec/views/programs/program_creator_spec.js index 56de3e1..4484a30 100644 --- a/cms/static/js/spec/views/programs/program_creator_spec.js +++ b/cms/static/js/spec/views/programs/program_creator_spec.js @@ -77,7 +77,7 @@ define([ spyOn( Router.prototype, 'goHome' ); sampleInput = { - category: 'xseries', + category: 'XSeries', organizations: 'test-org-key', name: 'Test Course Name', subtitle: 'Test Course Subtitle', @@ -131,7 +131,7 @@ define([ it( 'should submit the form correctly when creating micromasters program ', function(){ var programId = 221; - sampleInput.category = 'micromasters'; + sampleInput.category = 'MicroMasters'; completeForm( sampleInput ); diff --git a/cms/static/js/spec/views/programs/program_details_spec.js b/cms/static/js/spec/views/programs/program_details_spec.js index b84b123..1aacbfb 100644 --- a/cms/static/js/spec/views/programs/program_details_spec.js +++ b/cms/static/js/spec/views/programs/program_details_spec.js @@ -86,7 +86,7 @@ define([ } ], programData = { - category: 'xseries', + category: 'XSeries', course_codes: [{ display_name: 'test-course-display_name', key: 'test-course-key', diff --git a/cms/templates/js/programs/program_creator_form.underscore b/cms/templates/js/programs/program_creator_form.underscore index 4fe47ce..464d080 100644 --- a/cms/templates/js/programs/program_creator_form.underscore +++ b/cms/templates/js/programs/program_creator_form.underscore @@ -5,8 +5,8 @@ <label class="field-label" for="program-type"><%- gettext('Program type') %></label> <select id="program-type" class="field-input input-select program-type" name="category"> <option value=""><%- gettext('Select a type') %></option> - <option value="xseries"><%- gettext('XSeries') %></option> - <option value="micromasters"><%- gettext('MicroMasters') %></option> + <option value="XSeries"><%- gettext('XSeries') %></option> + <option value="MicroMasters"><%- gettext('MicroMasters') %></option> </select> <div class="field-message"> <span class="field-message-content"></span> diff --git a/openedx/core/djangoapps/programs/tests/mixins.py b/openedx/core/djangoapps/programs/tests/mixins.py index 3b6bf79..b3df487 100644 --- a/openedx/core/djangoapps/programs/tests/mixins.py +++ b/openedx/core/djangoapps/programs/tests/mixins.py @@ -99,11 +99,13 @@ class ProgramsDataMixin(object): ] } - def mock_programs_api(self, data=None, status_code=200): + def mock_programs_api(self, data=None, program_id='', status_code=200): """Utility for mocking out Programs API URLs.""" self.assertTrue(httpretty.is_enabled(), msg='httpretty must be enabled to mock Programs API calls.') url = ProgramsApiConfig.current().internal_api_url.strip('/') + '/programs/' + if program_id: + url += '{}/'.format(str(program_id)) if data is None: data = self.PROGRAMS_API_RESPONSE diff --git a/openedx/core/djangoapps/programs/tests/test_utils.py b/openedx/core/djangoapps/programs/tests/test_utils.py index 8f16be3..d20a4a3 100644 --- a/openedx/core/djangoapps/programs/tests/test_utils.py +++ b/openedx/core/djangoapps/programs/tests/test_utils.py @@ -40,8 +40,10 @@ ECOMMERCE_URL_ROOT = 'https://example-ecommerce.com' MARKETING_URL = 'https://www.example.com/marketing/path' -@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') +@ddt.ddt @attr('shard_2') +@httpretty.activate +@skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') class TestProgramRetrieval(ProgramsApiConfigMixin, ProgramsDataMixin, CredentialsDataMixin, CredentialsApiConfigMixin, CacheIsolationTestCase): """Tests covering the retrieval of programs from the Programs service.""" @@ -77,7 +79,6 @@ class TestProgramRetrieval(ProgramsApiConfigMixin, ProgramsDataMixin, Credential ) ] - @httpretty.activate def test_get_programs(self): """Verify programs data can be retrieved.""" self.create_programs_config() @@ -92,7 +93,24 @@ class TestProgramRetrieval(ProgramsApiConfigMixin, ProgramsDataMixin, Credential # Verify the API was actually hit (not the cache). self.assertEqual(len(httpretty.httpretty.latest_requests), 1) - @httpretty.activate + @ddt.data(True, False) + def test_get_programs_category_casing(self, is_detail): + """Temporary. Verify that program categories are lowercased.""" + self.create_programs_config() + + program = factories.Program(category='camelCase') + + if is_detail: + program_id = program['id'] + + self.mock_programs_api(data=program, program_id=program_id) + data = utils.get_programs(self.user, program_id=program_id) + self.assertEqual(data['category'], 'camelcase') + else: + self.mock_programs_api(data={'results': [program]}) + data = utils.get_programs(self.user) + self.assertEqual(data[0]['category'], 'camelcase') + def test_get_programs_caching(self): """Verify that when enabled, the cache is used for non-staff users.""" self.create_programs_config(cache_ttl=1) @@ -133,7 +151,6 @@ class TestProgramRetrieval(ProgramsApiConfigMixin, ProgramsDataMixin, Credential self.assertEqual(actual, []) self.assertTrue(mock_init.called) - @httpretty.activate def test_get_programs_data_retrieval_failure(self): """Verify behavior when data can't be retrieved from Programs.""" self.create_programs_config() @@ -142,7 +159,6 @@ class TestProgramRetrieval(ProgramsApiConfigMixin, ProgramsDataMixin, Credential actual = utils.get_programs(self.user) self.assertEqual(actual, []) - @httpretty.activate def test_get_programs_for_dashboard(self): """Verify programs data can be retrieved and parsed correctly.""" self.create_programs_config() @@ -165,7 +181,6 @@ class TestProgramRetrieval(ProgramsApiConfigMixin, ProgramsDataMixin, Credential actual = utils.get_programs_for_dashboard(self.user, self.COURSE_KEYS) self.assertEqual(actual, {}) - @httpretty.activate def test_get_programs_for_dashboard_no_data(self): """Verify behavior when no programs data is found for the user.""" self.create_programs_config() @@ -174,17 +189,6 @@ class TestProgramRetrieval(ProgramsApiConfigMixin, ProgramsDataMixin, Credential actual = utils.get_programs_for_dashboard(self.user, self.COURSE_KEYS) self.assertEqual(actual, {}) - @httpretty.activate - def test_get_programs_for_dashboard_invalid_data(self): - """Verify behavior when the Programs API returns invalid data and parsing fails.""" - self.create_programs_config() - invalid_program = {'invalid_key': 'invalid_data'} - self.mock_programs_api(data={'results': [invalid_program]}) - - actual = utils.get_programs_for_dashboard(self.user, self.COURSE_KEYS) - self.assertEqual(actual, {}) - - @httpretty.activate def test_get_program_for_certificates(self): """Verify programs data can be retrieved and parsed correctly for certificates.""" self.create_programs_config() @@ -199,7 +203,6 @@ class TestProgramRetrieval(ProgramsApiConfigMixin, ProgramsDataMixin, Credential self.assertEqual(len(actual), 2) self.assertEqual(actual, expected) - @httpretty.activate def test_get_program_for_certificates_no_data(self): """Verify behavior when no programs data is found for the user.""" self.create_programs_config() @@ -210,7 +213,6 @@ class TestProgramRetrieval(ProgramsApiConfigMixin, ProgramsDataMixin, Credential actual = utils.get_programs_for_credentials(self.user, program_credentials_data) self.assertEqual(actual, []) - @httpretty.activate def test_get_program_for_certificates_id_not_exist(self): """Verify behavior when no program with the given program_id in credentials exists. @@ -233,7 +235,6 @@ class TestProgramRetrieval(ProgramsApiConfigMixin, ProgramsDataMixin, Credential actual = utils.get_programs_for_credentials(self.user, credential_data) self.assertEqual(actual, []) - @httpretty.activate def test_get_display_category_success(self): self.create_programs_config() self.mock_programs_api() diff --git a/openedx/core/djangoapps/programs/utils.py b/openedx/core/djangoapps/programs/utils.py index 4a1c1f7..ebd6e5f 100644 --- a/openedx/core/djangoapps/programs/utils.py +++ b/openedx/core/djangoapps/programs/utils.py @@ -48,7 +48,17 @@ def get_programs(user, program_id=None): # Bypass caching for staff users, who may be creating Programs and want # to see them displayed immediately. cache_key = programs_config.CACHE_KEY if programs_config.is_cache_enabled and not user.is_staff else None - return get_edx_api_data(programs_config, user, 'programs', resource_id=program_id, cache_key=cache_key) + + data = get_edx_api_data(programs_config, user, 'programs', resource_id=program_id, cache_key=cache_key) + + # TODO: Temporary, to be removed once category names are cased for display. ECOM-5018. + if data and program_id: + data['category'] = data['category'].lower() + else: + for program in data: + program['category'] = program['category'].lower() + + return data def flatten_programs(programs, course_ids):