|
8 | 8 | from uuid import uuid4 |
9 | 9 |
|
10 | 10 | import ddt |
| 11 | +from django.test import SimpleTestCase, override_settings |
11 | 12 | from django.urls import NoReverseMatch |
12 | 13 | from django.urls import reverse |
13 | 14 | from opaque_keys import InvalidKeyError |
|
27 | 28 | from common.djangoapps.course_modes.tests.factories import CourseModeFactory |
28 | 29 | from common.djangoapps.student.models.course_enrollment import CourseEnrollment |
29 | 30 | from lms.djangoapps.courseware.models import StudentModule |
| 31 | +from lms.djangoapps.instructor.views.serializers_v2 import CourseInformationSerializerV2 |
30 | 32 | from lms.djangoapps.instructor_task.tests.factories import InstructorTaskFactory |
31 | 33 | from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase, TEST_DATA_SPLIT_MODULESTORE |
32 | 34 | from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory |
@@ -456,6 +458,44 @@ def test_bulk_email_tab_not_visible(self, feature_enabled, user_attribute, mock_ |
456 | 458 |
|
457 | 459 | self.assertNotIn('bulk_email', tab_ids) |
458 | 460 |
|
| 461 | + @patch('lms.djangoapps.instructor.views.serializers_v2.is_bulk_email_feature_enabled') |
| 462 | + @override_settings(COMMUNICATIONS_MICROFRONTEND_URL='http://localhost:1984') |
| 463 | + def test_bulk_email_tab_url_uses_communications_mfe(self, mock_bulk_email_enabled): |
| 464 | + """ |
| 465 | + Test that the bulk_email tab URL uses COMMUNICATIONS_MICROFRONTEND_URL, |
| 466 | + not INSTRUCTOR_MICROFRONTEND_URL. |
| 467 | + """ |
| 468 | + mock_bulk_email_enabled.return_value = True |
| 469 | + |
| 470 | + tabs = self._get_tabs_from_response(self.staff) |
| 471 | + bulk_email_tab = next((tab for tab in tabs if tab['tab_id'] == 'bulk_email'), None) |
| 472 | + |
| 473 | + self.assertIsNotNone(bulk_email_tab) |
| 474 | + expected_url = f'http://localhost:1984/courses/{self.course.id}/bulk_email' |
| 475 | + self.assertEqual(bulk_email_tab['url'], expected_url) |
| 476 | + |
| 477 | + @patch('lms.djangoapps.instructor.views.serializers_v2.is_bulk_email_feature_enabled') |
| 478 | + @override_settings(COMMUNICATIONS_MICROFRONTEND_URL=None) |
| 479 | + def test_bulk_email_tab_logs_warning_when_communications_mfe_url_not_set(self, mock_bulk_email_enabled): |
| 480 | + """ |
| 481 | + Test that a warning is logged when COMMUNICATIONS_MICROFRONTEND_URL is not set, |
| 482 | + and the resulting URL does not contain 'None'. |
| 483 | + """ |
| 484 | + mock_bulk_email_enabled.return_value = True |
| 485 | + |
| 486 | + with self.assertLogs('lms.djangoapps.instructor.views.serializers_v2', level='WARNING') as cm: |
| 487 | + tabs = self._get_tabs_from_response(self.staff) |
| 488 | + |
| 489 | + self.assertTrue( |
| 490 | + any('COMMUNICATIONS_MICROFRONTEND_URL is not configured' in msg for msg in cm.output) |
| 491 | + ) |
| 492 | + bulk_email_tab = next((tab for tab in tabs if tab['tab_id'] == 'bulk_email'), None) |
| 493 | + self.assertIsNotNone(bulk_email_tab) |
| 494 | + self.assertFalse( |
| 495 | + bulk_email_tab['url'].startswith('None'), |
| 496 | + f"Tab URL should not start with 'None': {bulk_email_tab['url']}" |
| 497 | + ) |
| 498 | + |
459 | 499 | def test_tabs_have_sort_order(self): |
460 | 500 | """ |
461 | 501 | Test that all tabs include a sort_order field. |
@@ -503,7 +543,7 @@ def test_tabs_log_warning_when_mfe_url_not_set(self): |
503 | 543 | tabs = self._get_tabs_from_response(self.staff) |
504 | 544 |
|
505 | 545 | self.assertTrue( |
506 | | - any('INSTRUCTOR_MICROFRONTEND_URL is not set' in msg for msg in cm.output) |
| 546 | + any('INSTRUCTOR_MICROFRONTEND_URL is not configured' in msg for msg in cm.output) |
507 | 547 | ) |
508 | 548 | # Tab URLs should use empty string as base, not "None" |
509 | 549 | for tab in tabs: |
@@ -534,6 +574,62 @@ def test_pacing_self_for_self_paced_course(self): |
534 | 574 | self.assertEqual(response.data['pacing'], 'self') |
535 | 575 |
|
536 | 576 |
|
| 577 | +class BuildTabUrlTest(SimpleTestCase): |
| 578 | + """ |
| 579 | + Unit tests for CourseInformationSerializerV2._build_tab_url. |
| 580 | +
|
| 581 | + Tests the helper directly to verify URL joining behavior without |
| 582 | + going through the full API stack. |
| 583 | + """ |
| 584 | + |
| 585 | + def _build(self, setting_name, *parts): |
| 586 | + return CourseInformationSerializerV2._build_tab_url(setting_name, *parts) # pylint: disable=protected-access |
| 587 | + |
| 588 | + @override_settings(INSTRUCTOR_MICROFRONTEND_URL='http://localhost:2003') |
| 589 | + def test_joins_base_and_path_parts(self): |
| 590 | + """Parts are joined with '/' separators.""" |
| 591 | + result = self._build('INSTRUCTOR_MICROFRONTEND_URL', 'instructor', 'course-v1:edX+DemoX+Demo', 'grading') |
| 592 | + self.assertEqual(result, 'http://localhost:2003/instructor/course-v1:edX+DemoX+Demo/grading') |
| 593 | + |
| 594 | + @override_settings(INSTRUCTOR_MICROFRONTEND_URL='http://localhost:2003/') |
| 595 | + def test_strips_trailing_slash_from_base(self): |
| 596 | + """A trailing slash on the base URL does not produce a double slash.""" |
| 597 | + result = self._build('INSTRUCTOR_MICROFRONTEND_URL', 'instructor', 'course-v1:edX+DemoX+Demo', 'grading') |
| 598 | + self.assertEqual(result, 'http://localhost:2003/instructor/course-v1:edX+DemoX+Demo/grading') |
| 599 | + |
| 600 | + @override_settings(INSTRUCTOR_MICROFRONTEND_URL='http://localhost:2003') |
| 601 | + def test_strips_slashes_from_path_parts(self): |
| 602 | + """Leading and trailing slashes on path parts are stripped before joining.""" |
| 603 | + result = self._build('INSTRUCTOR_MICROFRONTEND_URL', '/instructor/', '/course-v1:edX+DemoX+Demo/', '/grading/') |
| 604 | + self.assertEqual(result, 'http://localhost:2003/instructor/course-v1:edX+DemoX+Demo/grading') |
| 605 | + |
| 606 | + @override_settings(COMMUNICATIONS_MICROFRONTEND_URL=None) |
| 607 | + def test_logs_warning_and_returns_relative_url_when_setting_is_none(self): |
| 608 | + """When the setting is None, a warning is logged and the URL is relative (no 'None' prefix).""" |
| 609 | + with self.assertLogs('lms.djangoapps.instructor.views.serializers_v2', level='WARNING') as cm: |
| 610 | + result = self._build( |
| 611 | + 'COMMUNICATIONS_MICROFRONTEND_URL', 'courses', 'course-v1:edX+DemoX+Demo', 'bulk_email' |
| 612 | + ) |
| 613 | + |
| 614 | + self.assertTrue(any('COMMUNICATIONS_MICROFRONTEND_URL is not configured' in msg for msg in cm.output)) |
| 615 | + self.assertFalse(result.startswith('None')) |
| 616 | + self.assertEqual(result, '/courses/course-v1:edX+DemoX+Demo/bulk_email') |
| 617 | + |
| 618 | + def test_logs_warning_when_setting_does_not_exist(self): |
| 619 | + """When the setting name is not defined at all, behavior matches the None case.""" |
| 620 | + with self.assertLogs('lms.djangoapps.instructor.views.serializers_v2', level='WARNING') as cm: |
| 621 | + result = self._build('NONEXISTENT_MFE_URL', 'instructor', 'course-v1:edX+DemoX+Demo', 'grading') |
| 622 | + |
| 623 | + self.assertTrue(any('NONEXISTENT_MFE_URL is not configured' in msg for msg in cm.output)) |
| 624 | + self.assertEqual(result, '/instructor/course-v1:edX+DemoX+Demo/grading') |
| 625 | + |
| 626 | + @override_settings(COMMUNICATIONS_MICROFRONTEND_URL='http://localhost:1984/communications/') |
| 627 | + def test_base_with_subpath_and_trailing_slash(self): |
| 628 | + """Base URL with a subpath and trailing slash is joined cleanly.""" |
| 629 | + result = self._build('COMMUNICATIONS_MICROFRONTEND_URL', 'courses', 'course-v1:edX+DemoX+Demo', 'bulk_email') |
| 630 | + self.assertEqual(result, 'http://localhost:1984/communications/courses/course-v1:edX+DemoX+Demo/bulk_email') |
| 631 | + |
| 632 | + |
537 | 633 | @ddt.ddt |
538 | 634 | class InstructorTaskListViewTest(SharedModuleStoreTestCase): |
539 | 635 | """ |
|
0 commit comments