def setup(self, db, settings): # Set up data that's the same for standalone or multisite self.date_for = utc_yesterday() self.site = Site.objects.first() self.courses = [CourseOverviewFactory(), CourseOverviewFactory()] # Two for "our" course, one for another course in the same site self.enrollments = [ CourseEnrollmentFactory(course_id=self.courses[0].id), CourseEnrollmentFactory(course_id=self.courses[0].id), CourseEnrollmentFactory(course_id=self.courses[1].id), ] self.ce0_sm = StudentModuleFactory.from_course_enrollment( self.enrollments[0], created=as_datetime(self.date_for), modified=as_datetime(self.date_for)) # Handle site mode specifices if organizations_support_sites(): settings.FEATURES['FIGURES_IS_MULTISITE'] = True self.org = OrganizationFactory(sites=[self.site]) for course in self.courses: OrganizationCourseFactory(organization=self.org, course_id=str(course.id)) map_users_to_org(self.org, [ce.user for ce in self.enrollments]) # For our tests, we focus on a single enrollment. We should not # need to stand up other site data, but if we find we do need to, # then here's the place to do it else: self.org = OrganizationFactory()
def setup(self, db, settings): """ TODO: rework this so we have a top level object as a dict, then we can swap the sites in and out to test inclusion and exclusion """ settings.FEATURES['FIGURES_IS_MULTISITE'] = True is_multisite = figures.helpers.is_multisite() assert is_multisite self.data_start_date = DEFAULT_START_DATE self.data_end_date = DEFAULT_END_DATE self.alpha_site = SiteFactory(domain='alpha.site') self.alpha_course_overview = CourseOverviewFactory() self.alpha_course_daily_metrics = create_course_daily_metrics_data( site=self.alpha_site, start_date=self.data_start_date, end_date=self.data_end_date, course_id=self.alpha_course_overview.id) self.bravo_site = SiteFactory(domain='bravo.site') self.bravo_course_overview = CourseOverviewFactory() self.bravo_course_daily_metrics = create_course_daily_metrics_data( site=self.bravo_site, start_date=self.data_start_date, end_date=self.data_end_date, course_id=self.bravo_course_overview.id)
def simple_mau_test_data(settings): """ Pytest fixture to create the base test data we need for the MAU tests here We set up single site vs multisite mode in this fixture based on which edx-organizations package is declared in the pip requirements file used to run the tests: Community: edx-organizations==0.4.10 Tahoe: git+https://github.com/appsembler/[email protected] """ our_site = SiteFactory() our_org = OrganizationFactory() our_course = CourseOverviewFactory() our_other_course = CourseOverviewFactory() other_site = SiteFactory() other_site_course = CourseOverviewFactory() our_course_data = create_student_module_recs(our_course.id) our_other_course_sm = [ StudentModuleFactory(course_id=our_other_course.id) for i in range(10) ] month_for = date(year=our_course_data['year_for'], month=our_course_data['month_for'], day=1) expected_mau_ids = set( [rec.student.id for rec in our_course_data['in_range']]) test_data = dict( month_for=month_for, expected_mau_ids=expected_mau_ids, our_site=our_site, our_course=our_course, our_course_data=our_course_data, our_other_course=our_other_course, our_other_course_sm=our_other_course_sm, other_site=other_site, other_site_course=other_site_course, ) if organizations_support_sites(): settings.FEATURES['FIGURES_IS_MULTISITE'] = True our_org = OrganizationFactory(sites=[our_site]) for user in set([obj.student for obj in our_course_data['in_range']]): UserOrganizationMappingFactory(user=user, organization=our_org) for course_id in set( [obj.course_id for obj in our_course_data['in_range']]): OrganizationCourseFactory(organization=our_org, course_id=str(course_id)) return test_data
def make_course(**kwargs): return CourseOverviewFactory( id=as_course_key(kwargs['id']), display_name=kwargs['name'], org=kwargs['org'], number=kwargs['number'], )
def setup(self, db): self.data_start_date = DEFAULT_START_DATE self.data_end_date = DEFAULT_END_DATE self.course_overview = CourseOverviewFactory() self.course_daily_metrics = create_course_daily_metrics_data( self.data_start_date, self.data_end_date, course_id=self.course_overview.id)
def test_bulk_calculate_course_progress_unlinked_course_error(db, monkeypatch): """Tests 'bulk_calculate_course_progress_data' function The function under test iterates over a set of course enrollment records, So we create a couple of records to iterate over and mock the collect function """ course_overview = CourseOverviewFactory() course_enrollments = [ CourseEnrollmentFactory(course_id=course_overview.id) for i in range(2) ] mapping = { ce.course_id: LearnerCourseGradeMetricsFactory(course_id=str(ce.course_id), user=ce.user, sections_worked=1, sections_possible=2) for ce in course_enrollments } def mock_metrics(course_enrollment, **_kwargs): return mapping[course_enrollment.course_id] monkeypatch.setattr( 'figures.pipeline.enrollment_metrics.collect_metrics_for_enrollment', mock_metrics) with pytest.raises(UnlinkedCourseError) as e_info: data = bulk_calculate_course_progress_data(course_overview.id)
def test_bulk_calculate_course_progress_data_happy_path(db, monkeypatch): """Tests 'bulk_calculate_course_progress_data' function The function under test iterates over a set of course enrollment records, So we create a couple of records to iterate over and mock the collect function """ course_overview = CourseOverviewFactory() course_enrollments = [ CourseEnrollmentFactory(course_id=course_overview.id) for i in range(2) ] mapping = { ce.course_id: LearnerCourseGradeMetricsFactory(course_id=str(ce.course_id), user=ce.user, sections_worked=1, sections_possible=2) for ce in course_enrollments } def mock_metrics(course_enrollment, **_kwargs): return mapping[course_enrollment.course_id] monkeypatch.setattr( 'figures.pipeline.enrollment_metrics.get_site_for_course', lambda val: SiteFactory()) monkeypatch.setattr( 'figures.pipeline.enrollment_metrics.collect_metrics_for_enrollment', mock_metrics) data = bulk_calculate_course_progress_data(course_overview.id) assert data['average_progress'] == 0.5
def test_populate_course_mau(transactional_db, monkeypatch): expected_site = SiteFactory() course = CourseOverviewFactory() def mock_collect_course_mau(site, courselike, month_for=None, overwrite=False): assert site == expected_site assert courselike assert isinstance(month_for, date) return CourseMauMetricsFactory(), True monkeypatch.setattr('figures.tasks.collect_course_mau', mock_collect_course_mau) figures.tasks.populate_course_mau(site_id=expected_site.id, course_id=str(course.id)) # TODO: Create own test function figures.tasks.populate_course_mau(site_id=expected_site.id, course_id=str(course.id), month_for=None) figures.tasks.populate_course_mau(site_id=expected_site.id, course_id=str(course.id), month_for='2020-1-1')
def create_student_module_test_data(start_date, end_date): ''' NOTE: There are many combinations of unique students, courses, and student state. We're going to start with a relatively simple set 1. A single course 2. A single set per student (learner) 3. a small number of students to reduce test run time If we create a record per day then we can work off of a unique datapoint per day ''' student_modules = [] user = UserFactory() course_overview = CourseOverviewFactory() for dt in rrule(DAILY, dtstart=start_date, until=end_date): student_modules.append( StudentModuleFactory( student=user, course_id=course_overview.id, created=dt, modified=dt, )) # we'll return everything we create return dict( user=user, course_overview=course_overview, student_modules=student_modules, )
def setup(self, db): self.site = Site.objects.first() self.course_overview = CourseOverviewFactory() self.users = [UserFactory(), UserFactory()] self.course_access_roles = [ CourseAccessRoleFactory(user=self.users[0], course_id=self.course_overview.id, role='staff'), CourseAccessRoleFactory(user=self.users[1], course_id=self.course_overview.id, role='administrator'), ] self.serializer = GeneralCourseDataSerializer( instance=self.course_overview) self.expected_fields = [ 'course_id', 'course_name', 'course_code', 'org', 'start_date', 'end_date', 'self_paced', 'staff', 'metrics', ]
def setup(self, db): self.date_for = datetime.date(2018, 10, 1) self.site = Site.objects.first() self.users = [ UserFactory(date_joined=as_datetime(self.date_for - datetime.timedelta(days=60))) for i in range(0, 3) ] self.course_overviews = [ CourseOverviewFactory( created=as_datetime(self.date_for - datetime.timedelta(days=60))) for i in range(0, 3) ] self.cdm_recs = [ CourseDailyMetricsFactory(site=self.site, date_for=self.date_for, **cdm) for cdm in CDM_INPUT_TEST_DATA ] self.prev_day_sdm = SiteDailyMetricsFactory(site=self.site, date_for=prev_day( self.date_for), **SDM_DATA[1]) if is_multisite(): self.organization = OrganizationFactory(sites=[self.site]) for co in self.course_overviews: OrganizationCourseFactory(organization=self.organization, course_id=str(co.id)) if organizations_support_sites(): for user in self.users: UserOrganizationMappingFactory( user=user, organization=self.organization)
def test_latest_previous_record(self): course_overview = CourseOverviewFactory() # Create a set of records with non-continuous dates dates = [ datetime.date(2019, 10, 1), datetime.date(2019, 10, 2), datetime.date(2019, 10, 5), datetime.date(2019, 10, 29), datetime.date(2019, 11, 3), ] for rec_date in dates: cdms = CourseDailyMetricsFactory(site=self.site, course_id = course_overview.id, date_for=rec_date) rec = CourseDailyMetrics.latest_previous_record( site=self.site, course_id=course_overview.id) assert rec.date_for == dates[-1] rec2 = CourseDailyMetrics.latest_previous_record( site=self.site, course_id=course_overview.id, date_for=dates[-1]) assert rec2.date_for == dates[-2] rec3 = CourseDailyMetrics.latest_previous_record( site=self.site, course_id=course_overview.id, date_for=dates[0]) assert not rec3
def setup(self, db): self.course_overview = CourseOverviewFactory() self.users = [UserFactory(), UserFactory()] self.course_access_roles = [ CourseAccessRoleFactory(user=self.users[0], course_id=self.course_overview.id, role='staff'), CourseAccessRoleFactory(user=self.users[1], course_id=self.course_overview.id, role='administrator'), ] self.serializer = CourseDetailsSerializer( instance=self.course_overview) self.expected_fields = [ 'course_id', 'course_name', 'course_code', 'org', 'start_date', 'end_date', 'self_paced', 'staff', 'average_progress', 'learners_enrolled', 'average_days_to_complete', 'users_completed', ]
def course_test_data(): """Temporary fixture. Will remove as we abstract testing """ months_back = 6 site = SiteFactory() course_overview = CourseOverviewFactory() if organizations_support_sites(): org = OrganizationFactory(sites=[site]) else: org = OrganizationFactory() OrganizationCourseFactory(organization=org, course_id=str(course_overview.id)) enrollments = [ CourseEnrollmentFactory(course_id=course_overview.id) for i in range(3) ] users = [enrollment.user for enrollment in enrollments] student_modules = [] dates = generate_date_series(months_back=months_back) assert dates data_spec = list(zip(dates, list(range(months_back)))) return dict( site=site, org=org, users=users, course_overview=course_overview, enrollments=enrollments, student_modules=student_modules, months_back=months_back, dates=dates, data_spec=data_spec, )
def test_site_course_ids(monkeypatch): site = SiteFactory() course_overviews = [CourseOverviewFactory() for i in range(2)] if organizations_support_sites(): monkeypatch.setattr('figures.sites.is_multisite', lambda: True) our_org = OrganizationFactory(sites=[site]) # associate the course overviews with our org for co in course_overviews: OrganizationCourseFactory(course_id=co.id, organization=our_org) other_org = OrganizationFactory(sites=[SiteFactory()]) # create a course associated with another org co = CourseOverviewFactory() OrganizationCourseFactory(course_id=co.id, organization=other_org) course_ids = figures.sites.site_course_ids(site) assert set(course_ids) == set([str(co.id) for co in course_overviews])
def test_backfill_with_courses_arg_immediate(self, course_ids): """Test called with courses arg and not run in Celery worker This tests that the expected task function is called with specific parameters and the `.delay(course_id)` is not called. This and the following function are almost identical. They can be merged as one test method, but for now left as two test methods initially for readability and the additional development time to implement it. But, should we merge them, we add an additional parametrize decorator like so: ``` @pytest.mark.parametrize('delay_suffix, delay_flag', [ ('', False), ('.delay', True) ]) ``` And then we abstract the nested mocks to swap the `.has_calls` and `not called` checks (which might require a conditional) """ [CourseOverviewFactory(id=cid) for cid in course_ids] kwargs = {'courses': course_ids, 'use_celery': False} calls = [mock.call(course_id) for course_id in course_ids] with mock.patch(self.TASK) as mock_task: with mock.patch(self.TASK + '.delay') as mock_task_delay: call_command(self.MANAGEMENT_COMMAND, **kwargs) assert mock_task.has_calls(calls) assert not mock_task_delay.called
def setup(self, db): self.today = datetime.date(2018, 6, 1) self.course_overviews = [CourseOverviewFactory() for i in range(1, 3)] self.course_enrollments = [] for co in self.course_overviews: self.course_enrollments.extend( [CourseEnrollmentFactory(course_id=co.id) for i in range(1, 3)])
def test_get_course_enrollments_for_site_exclude_same_user_different_site( self): """ Test that CEs are not returned from course from another Site, in cases where a user has CEs in desired Site, but also in another Site. """ course_overviews = [CourseOverviewFactory() for i in range(2)] OrganizationCourseFactory(organization=self.organization, course_id=str(course_overviews[0].id)) OrganizationCourseFactory(organization=self.default_site_org, course_id=str(course_overviews[1].id)) uom_our_site = UserOrganizationMappingFactory( organization=self.organization) # enroll same user in a course associated w/ an Organization not connected to our Site uom_other_site = UserOrganizationMappingFactory( user=uom_our_site.user, organization=self.default_site_org) CourseEnrollmentFactory(course_id=course_overviews[1].id, user=uom_our_site.user) expected_ce = [ CourseEnrollmentFactory(course_id=course_overviews[0].id, user=uom_our_site.user) ] course_enrollments = figures.sites.get_course_enrollments_for_site( self.site) assert set([ce.id for ce in course_enrollments ]) == set([ce.id for ce in expected_ce])
def test_list_method_filter_method_course_ids(self, monkeypatch, enrollment_test_data): site = enrollment_test_data['site'] users = enrollment_test_data['users'] caller = self.make_caller(site, users) other_site = SiteFactory() assert site.domain != other_site.domain LearnerCourseGradeMetricsFactory(site=other_site) co = CourseOverviewFactory() LearnerCourseGradeMetricsFactory(site=site) co_lcgm = [LearnerCourseGradeMetricsFactory( site=site, course_id=str(co.id)) for i in range(5)] request_path = self.base_request_path + '?course_ids=' + str(co.id) response = self.make_request(request_path=request_path, monkeypatch=monkeypatch, site=site, caller=caller, action='list') assert response.status_code == status.HTTP_200_OK assert is_response_paginated(response.data) results = response.data['results'] # Check keys result_ids = [obj['id'] for obj in results] assert set(result_ids) == set([obj.id for obj in co_lcgm]) # Spot check the first record obj = LearnerCourseGradeMetrics.objects.get(id=results[0]['id']) self.check_serialized_data(results[0], obj)
def test_get_student_modules_for_course_in_site(self): course_overviews = [CourseOverviewFactory() for i in range(3)] for co in course_overviews[:-1]: OrganizationCourseFactory(organization=self.organization, course_id=str(co.id)) assert get_user_model().objects.count() == 0 user = UserFactory() UserOrganizationMappingFactory(user=user, organization=self.organization) sm_count = 2 sm_expected = [StudentModuleFactory(course_id=course_overviews[0].id, student=user ) for i in range(sm_count)] # StudentModule for other course StudentModuleFactory(course_id=course_overviews[1].id) # StudentModule for course not in organization StudentModuleFactory(course_id=course_overviews[2].id) sm = figures.sites.get_student_modules_for_course_in_site( site=self.site, course_id=course_overviews[0].id) assert sm.count() == len(sm_expected) sm = figures.sites.get_student_modules_for_site(site=self.site) assert sm.count() == len(sm_expected) + 1
def setup(self, db): ''' ''' self.date_for = DEFAULT_END_DATE self.course_count = 4 self.course_overviews = [CourseOverviewFactory( created=self.date_for) for i in range(self.course_count)]
def sm_test_data(db): """ WIP StudentModule test data to test MAU """ year_for = 2019 month_for = 10 created_date = datetime(year_for, month_for, 1).replace(tzinfo=utc) modified_date = datetime(year_for, month_for, 10).replace(tzinfo=utc) course_overviews = [CourseOverviewFactory() for i in range(3)] site = SiteFactory() sm = [] for co in course_overviews: sm += [StudentModuleFactory(course_id=co.id, created=created_date, modified=modified_date) for co in course_overviews] if organizations_support_sites(): org = OrganizationFactory(sites=[site]) for co in course_overviews: OrganizationCourseFactory(organization=org, course_id=str(co.id)) for rec in sm: UserOrganizationMappingFactory(user=rec.student, organization=org) else: org = OrganizationFactory() return dict(site=site, organization=org, course_overviews=course_overviews, student_modules=sm, year_for=year_for, month_for=month_for)
def setUp(self): self.User = get_user_model() self.users = [make_user(**data) for data in USER_DATA] self.course_overview = CourseOverviewFactory() self.course_enrollments = [ CourseEnrollmentFactory(course_id=self.course_overview.id, user=self.users[i]) for i in range(2)]
def test_get_site_for_course(self): """ """ with mock.patch('figures.helpers.settings.FEATURES', self.features): co = CourseOverviewFactory() site = figures.sites.get_site_for_course(str(co.id)) assert site == Site.objects.first()
def test_get_site_for_course_not_in_site(self): """ We create a course but don't add the course to OrganizationCourse We expect that a site cannot be found """ co = CourseOverviewFactory() site = figures.sites.get_site_for_course(str(co.id)) assert not site
def test_get_staff_with_no_course(self): '''Create a serializer for a course with a different ID than for the data we set up. This simulates when there are no staff members for the given course, which will have a different ID than the one we created in the setup method ''' assert CourseDetailsSerializer().get_staff( CourseOverviewFactory()) == []
def test_get_course_keys_for_site(self, course_count): sites = Site.objects.all() assert sites.count() == 1 with mock.patch('figures.helpers.settings.FEATURES', self.features): course_overviews = [CourseOverviewFactory() for i in range(course_count)] course_keys = figures.sites.get_course_keys_for_site(sites[0]) expected_ids = [str(co.id) for co in course_overviews] assert set([str(key) for key in course_keys]) == set(expected_ids)
def test_get_courses_for_site(self, course_count): course_overviews = [CourseOverviewFactory() for i in range(course_count)] for co in course_overviews: OrganizationCourseFactory(organization=self.organization, course_id=str(co.id)) courses = figures.sites.get_courses_for_site(self.site) expected_ids = [str(co.id) for co in course_overviews] assert set([str(co.id) for co in courses]) == set(expected_ids)
def setup(self, db, settings): super(TestLearnerDetailsViewSetMultisite, self).setup(db) # TODO:REFACTOR:Make base 'multisite scaffolding' view test class to # set up the sites, orgs, and users. Put into tests/views/base.py settings.FEATURES['FIGURES_IS_MULTISITE'] = True is_multisite = figures.helpers.is_multisite() assert is_multisite self.my_site_org = OrganizationFactory(sites=[self.site]) self.other_site = SiteFactory(domain='other-site.test') self.other_site_org = OrganizationFactory(sites=[self.other_site]) self.my_course_overviews = [ CourseOverviewFactory() for i in range(0, 4) ] for co in self.my_course_overviews: OrganizationCourseFactory(organization=self.my_site_org, course_id=str(co.id)) # Set up users and enrollments for 'my site' self.my_site_users = [UserFactory() for i in range(3)] for user in self.my_site_users: UserOrganizationMappingFactory(user=user, organization=self.my_site_org) # Create a mix of enrollments: # one learner in one course, same for the other, then two learners in # the same course, and keep one course w/out learners self.my_enrollments = [ CourseEnrollmentFactory(course=self.my_course_overviews[0], user=self.my_site_users[0]), CourseEnrollmentFactory(course=self.my_course_overviews[1], user=self.my_site_users[1]), CourseEnrollmentFactory(course=self.my_course_overviews[2], user=self.my_site_users[0]), CourseEnrollmentFactory(course=self.my_course_overviews[2], user=self.my_site_users[1]), ] self.caller = UserFactory() UserOrganizationMappingFactory(user=self.caller, organization=self.my_site_org, is_amc_admin=True) self.my_site_users.append(self.caller) # Set up other site's data self.other_site_enrollment = CourseEnrollmentFactory() OrganizationCourseFactory( organization=self.other_site_org, course_id=self.other_site_enrollment.course.id) UserOrganizationMappingFactory(user=self.other_site_enrollment.user, organization=self.other_site_org) self.expected_result_keys = [ 'id', 'username', 'name', 'email', 'country', 'is_active', 'year_of_birth', 'level_of_education', 'gender', 'date_joined', 'bio', 'courses', 'language_proficiencies', 'profile_image' ]
def test_bulk_calculate_course_progress_no_enrollments(db, monkeypatch): """This tests when there is a new course with no enrollments """ monkeypatch.setattr( 'figures.pipeline.enrollment_metrics.get_site_for_course', lambda val: SiteFactory()) course_overview = CourseOverviewFactory() data = bulk_calculate_course_progress_data(course_overview.id) assert data['average_progress'] == 0.0