def test_get_course_availability(mitx_valid_data): """ Test that availability is calculated as expected """ ocw_course = CourseFactory.create(platform=PlatformType.ocw.value) # test mitx course with raw_json assert get_course_availability(ocw_course) == AvailabilityType.current.value mitx_course_with_json = CourseFactory.create( course_id=mitx_valid_data["course_runs"][0]["key"], raw_json=mitx_valid_data, platform=PlatformType.mitx.value, ) # test mitx course without raw_json assert ( get_course_availability(mitx_course_with_json) == mitx_valid_data["course_runs"][0]["availability"] ) mitx_course_no_json = CourseFactory.create( raw_json=None, platform=PlatformType.mitx.value ) assert get_course_availability(mitx_course_no_json) is None # test mitx course without course_runs mitx_valid_data["course_runs"] = None # pop course_runs json mitx_course_no_runs_json = CourseFactory.create( raw_json=mitx_valid_data, platform=PlatformType.mitx.value ) assert get_course_availability(mitx_course_no_runs_json) is None
def test_list_course_endpoint(client): """Test course endpoint""" course = CourseFactory.create() # this should be filtered out CourseFactory.create(runs=None) resp = client.get(reverse("courses-list")) assert resp.data.get("count") == 1 assert resp.data.get("results")[0]["id"] == course.id
def test_parse_mitx_json_data_overwrite(mocker, force_overwrite): """ Test that valid mitx json data is skipped if it doesn't need an update """ CourseFactory.create(course_id=mitx_valid_data["course_runs"][0]["key"], last_modified=datetime.now().astimezone(pytz.utc)) mock_save = mocker.patch( 'course_catalog.tasks_helpers.CourseSerializer.save') parse_mitx_json_data(mitx_valid_data, force_overwrite=force_overwrite) assert mock_save.call_count == (1 if force_overwrite else 0)
def test_featured_courses_endpoint(client, featured): """Test featured courses endpoint""" course = CourseFactory.create(featured=featured) # this should be filtered out CourseFactory.create(runs=None) resp = client.get(reverse("courses-list") + "featured/") assert resp.data.get("count") == (1 if featured else 0) if featured: assert resp.data.get("results")[0]["id"] == course.id
def test_upcoming_courses_endpoint(client, kwargs, is_upcoming): """Test upcoming courses endpoint""" course = CourseFactory.create(runs=None) LearningResourceRunFactory.create(content_object=course, **kwargs) # this should be filtered out CourseFactory.create(runs=None) resp = client.get(reverse("courses-list") + "upcoming/") assert resp.data.get("count") == (1 if is_upcoming else 0) if is_upcoming: assert resp.data.get("results")[0]["id"] == course.id
def test_get_course_endpoint(client): """Test course detail endpoint""" course = CourseFactory.create() resp = client.get(reverse("courses-detail", args=[course.id])) assert resp.data.get("course_id") == course.course_id
def test_load_run(run_exists): """Test that load_run loads the course run""" course = CourseFactory.create(runs=None) learning_resource_run = (LearningResourceRunFactory.create( content_object=course) if run_exists else LearningResourceRunFactory.build()) props = model_to_dict( LearningResourceRunFactory.build( run_id=learning_resource_run.run_id, platform=learning_resource_run.platform)) del props["content_type"] del props["object_id"] del props["id"] assert LearningResourceRun.objects.count() == (1 if run_exists else 0) result = load_run(course, props) assert LearningResourceRun.objects.count() == 1 assert result.content_object == course # assert we got a course run back assert isinstance(result, LearningResourceRun) for key, value in props.items(): assert getattr(result, key) == value, f"Property {key} should equal {value}"
def test_favorites_serializer(): """ Test that the favorite serializer generic foreign key works and also rejects unexpected classes """ user = UserFactory.create() course = CourseFactory.create() bootcamp = BootcampFactory.create() user_list = UserListFactory.create(author=user) program = ProgramFactory.create() course_topic = CourseTopicFactory.create() favorite_item = FavoriteItem(user=user, item=course) serializer = FavoriteItemSerializer(favorite_item) assert serializer.data.get("content_data") == CourseSerializer(course).data favorite_item = FavoriteItem(user=user, item=bootcamp) serializer = FavoriteItemSerializer(favorite_item) assert serializer.data.get("content_data") == BootcampSerializer( bootcamp).data favorite_item = FavoriteItem(user=user, item=user_list) serializer = FavoriteItemSerializer(favorite_item) assert serializer.data.get("content_data") == UserListSerializer( user_list).data favorite_item = FavoriteItem(user=user, item=program) serializer = FavoriteItemSerializer(favorite_item) assert serializer.data.get("content_data") == ProgramSerializer( program).data favorite_item = FavoriteItem(user=user, item=course_topic) serializer = FavoriteItemSerializer(favorite_item) with pytest.raises(Exception): assert serializer.data.get("content_data").get("id") == course_topic.id
def test_es_course_serializer(offered_by): """ Test that ESCourseSerializer correctly serializes a course object """ course = CourseFactory.create(offered_by=offered_by) serialized = ESCourseSerializer(course).data assert_json_equal( serialized, { "object_type": COURSE_TYPE, "id": course.id, "course_id": course.course_id, "coursenum": course.course_id.split("+")[-1], "short_description": course.short_description, "full_description": course.full_description, "platform": course.platform, "title": course.title, "image_src": course.image_src, "topics": list(course.topics.values_list("name", flat=True)), "runs": [ ESRunSerializer(course_run).data for course_run in course.runs.order_by("-best_start_date") ], "published": True, "offered_by": list(course.offered_by.values_list("name", flat=True)), "created": drf_datetime(course.created_on), "default_search_priority": 1, "minimum_price": minimum_price(course), "resource_relations": {"name": "resource"}, }, )
def test_sync_ocw_course_files(mock_ocw_learning_bucket, mocker, with_error): """Test that sync_ocw_course_files calls load_content_files for each run""" fake_data = '{"key": "data"}' mock_log = mocker.patch("course_catalog.api.log.exception") mock_transform = mocker.patch( "course_catalog.api.transform_content_files", return_value=fake_data ) mock_load_content_files = mocker.patch( "course_catalog.api.load_content_files", autospec=True, return_value=[] ) if with_error: mock_load_content_files.side_effect = Exception course = CourseFactory.create(published=True, platform=PlatformType.ocw.value) runs = course.runs.all() for run in runs: mock_ocw_learning_bucket.bucket.put_object( Key="{}/{}_master.json".format(run.url.split("/")[-1], run.run_id), Body=fake_data, ACL="public-read", ) sync_ocw_course_files(ids=[course.id]) assert mock_load_content_files.call_count == len(runs) for run in runs: mock_load_content_files.assert_any_call(run, mock_transform.return_value) if with_error: mock_log.assert_any_call("Error syncing files for course run %d", run.id)
def test_upsert_course(mocker): """ Tests that upsert_course calls update_field_values_by_query with the right parameters """ patched_task = mocker.patch("search.tasks.upsert_course") course = CourseFactory.create() upsert_course(course.id) patched_task.delay.assert_called_once_with(course.id)
def test_content_type_interactions_serializer_valid(): """Verifies that valid data results in no errors""" course = CourseFactory.create() serializer = ContentTypeInteractionSerializer( data={ "interaction_type": "view", "content_type": "course", "content_id": course.id, }) assert serializer.is_valid() is True assert serializer.errors == {}
def test_serialize_course_for_bulk(): """ Test that serialize_course_for_bulk yields a valid ESCourseSerializer """ course = CourseFactory.create() assert_json_equal( serialize_course_for_bulk(course), { "_id": gen_course_id(course.platform, course.course_id), **ESCourseSerializer(course).data, }, )
def test_generate_duplicates_yaml(settings, mocker): """Test for generate_duplicates_yaml""" duplicate_course1 = CourseFactory.create(title="course1", platform="mitx") desired_course1 = CourseFactory.create(title="course1", platform="mitx") extra_offered_by = LearningResourceOfferorFactory.create() desired_course1.offered_by.add(extra_offered_by) duplicate_course2 = CourseFactory.create(title="course2", platform="mitx") desired_course2 = CourseFactory.create(title="course2", platform="mitx") settings.DUPLICATE_COURSES_URL = "url" duplicate_file_content = f"""mitx: - course_id: {desired_course2.course_id} duplicate_course_ids: - {desired_course2.course_id} - {duplicate_course2.course_id} title: course2 """ mocker.patch( "requests.get", autospec=True, return_value=mocker.Mock(text=duplicate_file_content), ) expected_output = f"""mitx: - course_id: {desired_course1.course_id} duplicate_course_ids: - {desired_course1.course_id} - {duplicate_course1.course_id} title: course1 - course_id: {desired_course2.course_id} duplicate_course_ids: - {desired_course2.course_id} - {duplicate_course2.course_id} title: course2 """ assert generate_duplicates_yaml() == expected_output
def test_delete_course(mocker): """ Tests that delete_course calls the delete tasks for the course and its content files """ patched_delete_task = mocker.patch("search.task_helpers.delete_document") course = CourseFactory.create() course_es_id = gen_course_id(course.platform, course.course_id) content_files = [ContentFileFactory.create(run=run) for run in course.runs.all()] delete_course(course) patched_delete_task.delay.assert_any_call(course_es_id, COURSE_TYPE) for content_file in content_files: patched_delete_task.delay.assert_any_call( gen_content_file_id(content_file.key), COURSE_TYPE, routing=course_es_id )
def test_course_detail_endpoint_lists(user_client, user): """Test that author's list ids are included""" course = CourseFactory.create() user_lists = UserListFactory.create_batch(3, author=user) list_items = sorted( [ UserListItemFactory.create(content_object=course, user_list=user_list) for user_list in user_lists ], key=lambda x: x.id, ) resp = user_client.get(reverse("courses-detail", args=[course.id])) assert sorted(resp.data.get("lists"), key=lambda x: x["item_id"]) == [ MicroUserListItemSerializer(list_item).data for list_item in list_items ]
def test_bulk_index_content_files(mocked_es, mocker, settings, errors, indexing_func_name, doc): # pylint: disable=too-many-arguments """ index functions for content files should call bulk with correct arguments """ settings.ELASTICSEARCH_INDEXING_CHUNK_SIZE = 3 course = CourseFactory.create() run = LearningResourceRunFactory.create(content_object=course) content_files = ContentFileFactory.create_batch(5, run=run) mock_get_aliases = mocker.patch("search.indexing_api.get_active_aliases", autospec=True, return_value=["a", "b"]) bulk_mock = mocker.patch("search.indexing_api.bulk", autospec=True, return_value=(0, errors)) mocker.patch( f"search.indexing_api.serialize_content_file_for_bulk", autospec=True, return_value=doc, ) mocker.patch( f"search.indexing_api.serialize_content_file_for_bulk_deletion", autospec=True, return_value=doc, ) index_func = getattr(indexing_api, indexing_func_name) if errors: with pytest.raises(ReindexException): index_func(run.id) else: index_func(run.id) for alias in mock_get_aliases.return_value: for chunk in chunks( [doc for _ in content_files], chunk_size=settings.ELASTICSEARCH_INDEXING_CHUNK_SIZE, ): bulk_mock.assert_any_call( mocked_es.conn, chunk, index=alias, doc_type=GLOBAL_DOC_TYPE, chunk_size=settings.ELASTICSEARCH_INDEXING_CHUNK_SIZE, routing=gen_course_id(course.platform, course.course_id), )
def test_find_similar_resources(mocker, client): """The view should return the results of the API method for finding similar resources""" course = CourseFactory.create() doc_vals = { "id": course.id, "object_id": COURSE_TYPE, "title": course.title, "short_description": course.short_description, } fake_response = {"similar": "resources"} similar_resources_mock = mocker.patch( "search.views.find_similar_resources", autospec=True, return_value=fake_response) resp = client.post(reverse("similar-resources"), data=doc_vals) assert resp.json() == fake_response similar_resources_mock.assert_called_once_with(user=AnonymousUser(), value_doc=doc_vals)
def test_user_list_items_endpoint_create_item_bad_data(client, user): """Test userlistitems endpoint for creating a UserListItem""" userlist = UserListFactory.create(author=user, privacy_level=PrivacyLevel.public.value) course = CourseFactory.create() client.force_login(user) data = {"content_type": "bad_content", "object_id": course.id} resp = client.post(reverse("userlistitems-list", args=[userlist.id]), data=data, format="json") assert resp.status_code == 400 assert resp.json() == { "non_field_errors": ["Incorrect object type bad_content"], "error_type": "ValidationError", }
def test_user_list_items_endpoint_create_item(client, user, is_author, mock_user_list_index): """Test userlistitems endpoint for creating a UserListItem""" author = UserFactory.create() userlist = UserListFactory.create(author=author, privacy_level=PrivacyLevel.public.value) course = CourseFactory.create() client.force_login(author if is_author else user) data = {"content_type": "course", "object_id": course.id} resp = client.post(reverse("userlistitems-list", args=[userlist.id]), data=data, format="json") assert resp.status_code == (201 if is_author else 403) if resp.status_code == 201: assert resp.json().get("object_id") == course.id mock_user_list_index.upsert_user_list.assert_called_once_with( userlist.id)
def test_load_course(mock_upsert_tasks, course_exists, is_published, blacklisted): """Test that load_course loads the course""" course = (CourseFactory.create(runs=None, published=is_published) if course_exists else CourseFactory.build()) assert Course.objects.count() == (1 if course_exists else 0) assert LearningResourceRun.objects.count() == 0 props = model_to_dict( CourseFactory.build(course_id=course.course_id, platform=course.platform, published=is_published)) del props["id"] run = model_to_dict( LearningResourceRunFactory.build(platform=course.platform)) del run["content_type"] del run["object_id"] del run["id"] props["runs"] = [run] blacklist = [course.course_id] if blacklisted else [] result = load_course(props, blacklist, []) if course_exists and not is_published and not blacklisted: mock_upsert_tasks.delete_course.assert_called_with(result) elif is_published and not blacklisted: mock_upsert_tasks.upsert_course.assert_called_with(result.id) else: mock_upsert_tasks.delete_program.assert_not_called() mock_upsert_tasks.upsert_course.assert_not_called() assert Course.objects.count() == 1 assert LearningResourceRun.objects.count() == 1 # assert we got a course back assert isinstance(result, Course) for key, value in props.items(): assert getattr(result, key) == value, f"Property {key} should equal {value}"
def test_popular_content_serializer(mocker, is_deleted, user): """Test PopularContentSerializer""" resources = [ VideoFactory.create(), ProgramFactory.create(), CourseFactory.create(), UserListFactory.create(), BootcampFactory.create(), ] data = [{ "content_type_id": ContentType.objects.get_for_model(resource).id, "content_id": resource.id, } for resource in resources] if is_deleted: for resource in resources: resource.delete() resources = [] resources = [ type(resource).objects.filter( id=resource.id).prefetch_list_items_for_user( user).annotate_is_favorite_for_user(user).first() for resource in resources ] context = {"request": mocker.Mock(user=user)} # NOTE: we test PopularContentSerializer instead of PopularContentListSerializer # because the list serializer is never used directly, but rather many=True tells # PopularContentSerializer to delegate to PopularContentListSerializer results = PopularContentSerializer(data, context=context, many=True).data # should be sorted by the same order they were passed in assert_json_equal( results, [ GenericForeignKeyFieldSerializer(resource, context=context).data for resource in resources ], )
def test_course_report(client): """Test ocw course report""" CourseFactory.create( platform=PlatformType.ocw.value, learning_resource_type=ResourceType.course.value, published=False, ) CourseFactory.create( platform=PlatformType.ocw.value, learning_resource_type=ResourceType.course.value, published=True, image_src="", ) CourseFactory.create( platform=PlatformType.ocw.value, learning_resource_type=ResourceType.course.value, published=True, image_src="abc123", ) CourseFactory.create( platform=PlatformType.ocw.value, learning_resource_type=ResourceType.ocw_resource.value, published=False, ) username = "******" password = "******" User.objects.create_user(username=username, password=password) client.login(username=username, password=password) resp = client.get(reverse("ocw-course-report")) assert resp.data == { "total_number_of_ocw_courses": 3, "published_ocw_courses_with_image": 2, "unpublished_ocw_courses": 1, "ocw_courses_without_image": 1, "ocw_resources": 1, }
def deserializing_a_valid_ocw_course_with_existing_newer_run( mock_course_index_functions, ocw_valid_data ): """ Verify that course values are not overwritten if the course already has a newer run """ course = CourseFactory.create( platform=PlatformType.ocw.value, course_id=ocw_valid_data["course_id"], title="existing", ) assert course.runs.count() == 3 existing_run = course.runs.first() existing_run.best_start_date = datetime.now(timezone.utc) existing_run.save() digest_ocw_course(ocw_valid_data, timezone.now(), True) assert Course.objects.count() == 1 course = Course.objects.last() assert course.title == "existing" assert course.runs.count() == 4
def test_content_type_interaction_create(client): """Test that the content type interaction API creates a record""" course = CourseFactory.create() content_type = ContentType.objects.get_for_model(course) assert ContentTypeInteraction.objects.count() == 0 payload = { "interaction_type": "view", "content_type": content_type.name, "content_id": course.id, } response = client.post(reverse("interactions-list"), payload) assert response.status_code == status.HTTP_201_CREATED assert response.json() == payload assert ContentTypeInteraction.objects.count() == 1 interaction = ContentTypeInteraction.objects.first() assert interaction.interaction_type == "view" assert interaction.content == course
def test_upload_ocw_master_json(settings, mocker): """ Test that ocw_upload_master_json uploads to S3 """ setup_s3(settings) course = CourseFactory.create(platform=PlatformType.ocw.value) course.url = "http://ocw.mit.edu/courses/brain-and-cognitive-sciences/9-15-biochemistry-and-pharmacology-of-synaptic-transmission-fall-2007" course.raw_json = {"test": "json"} course.save() upload_ocw_master_json.delay() s3 = boto3.resource( "s3", aws_access_key_id=settings.OCW_LEARNING_COURSE_BUCKET_NAME, aws_secret_access_key=settings.OCW_LEARNING_COURSE_ACCESS_KEY, ) # The filename was pulled from the uid 1.json in the TEST_JSON_PATH files. obj = s3.Object( settings.OCW_LEARNING_COURSE_BUCKET_NAME, f"9-15-biochemistry-and-pharmacology-of-synaptic-transmission-fall-2007/{course.course_id}_master.json", ) assert json.loads(obj.get()["Body"].read())
def test_load_duplicate_course(mock_upsert_tasks, course_exists, course_id_is_duplicate, duplicate_course_exists): """Test that load_course loads the course""" course = CourseFactory.create( runs=None) if course_exists else CourseFactory.build() duplicate_course = (CourseFactory.create(runs=None, platform=course.platform) if duplicate_course_exists else CourseFactory.build()) if course_exists and duplicate_course_exists: assert Course.objects.count() == 2 elif course_exists or duplicate_course_exists: assert Course.objects.count() == 1 else: assert Course.objects.count() == 0 assert LearningResourceRun.objects.count() == 0 duplicates = [{ "course_id": course.course_id, "duplicate_course_ids": [course.course_id, duplicate_course.course_id], }] course_id = (duplicate_course.course_id if course_id_is_duplicate else course.course_id) props = model_to_dict( CourseFactory.build(course_id=course_id, platform=course.platform)) del props["id"] run = model_to_dict( LearningResourceRunFactory.build(platform=course.platform)) del run["content_type"] del run["object_id"] del run["id"] props["runs"] = [run] result = load_course(props, [], duplicates) if course_id_is_duplicate and duplicate_course_exists: mock_upsert_tasks.delete_course.assert_called() mock_upsert_tasks.upsert_course.assert_called_with(result.id) assert Course.objects.count() == (2 if duplicate_course_exists else 1) assert LearningResourceRun.objects.count() == 1 # assert we got a course back assert isinstance(result, Course) saved_course = Course.objects.filter(course_id=course.course_id).first() for key, value in props.items(): assert getattr(result, key) == value, f"Property {key} should equal {value}" assert ( getattr(saved_course, key) == value ), f"Property {key} should be updated to {value} in the database"
def test_load_program( mock_upsert_tasks, program_exists, is_published, courses_exist, has_prices, has_retired_course, ): # pylint: disable=too-many-arguments """Test that load_program loads the program""" program = (ProgramFactory.create(published=is_published, runs=[]) if program_exists else ProgramFactory.build(published=is_published, runs=[])) courses = (CourseFactory.create_batch(2, platform="fake-platform") if courses_exist else CourseFactory.build_batch( 2, platform="fake-platform")) prices = CoursePriceFactory.build_batch(2) if has_prices else [] before_course_count = len(courses) if courses_exist else 0 after_course_count = len(courses) if program_exists and has_retired_course: course = CourseFactory.create(platform="fake-platform") before_course_count += 1 after_course_count += 1 ProgramItem.objects.create( program=program, content_type=ContentType.objects.get(model="course"), object_id=course.id, position=1, ) assert program.items.count() == 1 else: assert program.items.count() == 0 assert Program.objects.count() == (1 if program_exists else 0) assert Course.objects.count() == before_course_count run_data = { "prices": [{ "price": price.price, "mode": price.mode, "upgrade_deadline": price.upgrade_deadline, } for price in prices], "platform": PlatformType.mitx.value, "run_id": program.program_id, "enrollment_start": "2017-01-01T00:00:00Z", "start_date": "2017-01-20T00:00:00Z", "end_date": "2017-06-20T00:00:00Z", "best_start_date": "2017-06-20T00:00:00Z", "best_end_date": "2017-06-20T00:00:00Z", } result = load_program( { "program_id": program.program_id, "title": program.title, "url": program.url, "image_src": program.image_src, "published": is_published, "runs": [run_data], "courses": [{ "course_id": course.course_id, "platform": course.platform } for course in courses], }, [], [], ) if program_exists and not is_published: mock_upsert_tasks.delete_program.assert_called_with(result) elif is_published: mock_upsert_tasks.upsert_program.assert_called_with(result.id) else: mock_upsert_tasks.delete_program.assert_not_called() mock_upsert_tasks.upsert_program.assert_not_called() assert Program.objects.count() == 1 assert Course.objects.count() == after_course_count # assert we got a program back and that each course is in a program assert isinstance(result, Program) assert result.items.count() == len(courses) assert result.runs.count() == 1 assert result.runs.first().prices.count() == len(prices) assert sorted([(price.price, price.mode, price.upgrade_deadline) for price in result.runs.first().prices.all()]) == sorted([ (price.price, price.mode, price.upgrade_deadline) for price in prices ]) assert result.runs.first().best_start_date == _parse_datetime( run_data["best_start_date"]) for item, data in zip(result.items.all(), courses): course = item.item assert isinstance(course, Course) assert course.course_id == data.course_id