fix: Remove B2C Subscriptions (#35303)

REV-3697
This commit is contained in:
Juliana Kang
2024-09-04 14:01:45 -04:00
committed by GitHub
parent ef4e03729e
commit 51d538cbe7
50 changed files with 9 additions and 1709 deletions

View File

@@ -3,16 +3,11 @@
import uuid
from unittest import mock
from django.conf import settings
from requests import Response
from requests.exceptions import HTTPError
from common.djangoapps.student.tests.factories import UserFactory
from openedx.core.djangoapps.credentials.models import CredentialsApiConfig
from openedx.core.djangoapps.credentials.tests import factories
from openedx.core.djangoapps.credentials.tests.mixins import CredentialsApiConfigMixin
from openedx.core.djangoapps.credentials.utils import (
get_courses_completion_status,
get_credentials,
get_credentials_records_url,
)
@@ -107,33 +102,3 @@ class TestGetCredentials(CredentialsApiConfigMixin, CacheIsolationTestCase):
result = get_credentials_records_url("abcdefgh-ijkl-mnop-qrst-uvwxyz123456")
assert result == "https://credentials.example.com/records/programs/abcdefghijklmnopqrstuvwxyz123456"
@mock.patch("requests.Response.raise_for_status")
@mock.patch("requests.Response.json")
@mock.patch(UTILS_MODULE + ".get_credentials_api_client")
def test_get_courses_completion_status(self, mock_get_api_client, mock_json, mock_raise):
"""
Test to verify the functionality of get_courses_completion_status
"""
UserFactory.create(username=settings.CREDENTIALS_SERVICE_USERNAME)
course_statuses = factories.UserCredentialsCourseRunStatus.create_batch(3)
response_data = [course_status["course_run"]["key"] for course_status in course_statuses]
mock_raise.return_value = None
mock_json.return_value = {
"lms_user_id": self.user.id,
"status": course_statuses,
"username": self.user.username,
}
mock_get_api_client.return_value.post.return_value = Response()
course_run_keys = [course_status["course_run"]["key"] for course_status in course_statuses]
api_response, is_exception = get_courses_completion_status(self.user.id, course_run_keys)
assert api_response == response_data
assert is_exception is False
@mock.patch("requests.Response.raise_for_status")
def test_get_courses_completion_status_api_error(self, mock_raise):
mock_raise.return_value = HTTPError("An Error occured")
UserFactory.create(username=settings.CREDENTIALS_SERVICE_USERNAME)
api_response, is_exception = get_courses_completion_status(self.user.id, ["fake1", "fake2", "fake3"])
assert api_response == []
assert is_exception is True

View File

@@ -5,7 +5,6 @@ from typing import Dict, List
from urllib.parse import urljoin
import requests
from django.conf import settings
from django.contrib.auth import get_user_model
from edx_rest_api_client.auth import SuppliedJwtAuth
@@ -121,59 +120,3 @@ def get_credentials(
cache_key=cache_key,
raise_on_error=raise_on_error,
)
def get_courses_completion_status(username, course_run_ids):
"""
Given the username and course run ids, checks for course completion status
Arguments:
username (User): Username of the user whose credentials are being requested.
course_run_ids(List): list of course run ids for which we need to check the completion status
Returns:
list of course_run_ids for which user has completed the course
Boolean: True if an exception occurred while calling the api, False otherwise
"""
credential_configuration = CredentialsApiConfig.current()
if not credential_configuration.enabled:
log.warning("%s configuration is disabled.", credential_configuration.API_NAME)
return [], False
completion_status_url = f"{settings.CREDENTIALS_INTERNAL_SERVICE_URL}/api" "/credentials/v1/learner_cert_status/"
try:
api_client = get_credentials_api_client(User.objects.get(username=settings.CREDENTIALS_SERVICE_USERNAME))
api_response = api_client.post(
completion_status_url,
json={
"username": username,
"course_runs": course_run_ids,
},
)
api_response.raise_for_status()
course_completion_response = api_response.json()
except Exception as exc: # pylint: disable=broad-except
log.exception(
"An unexpected error occurred while reqeusting course completion statuses "
"for user [%s] for course_run_ids [%s] with exc [%s]:",
username,
course_run_ids,
exc,
)
return [], True
log.info(
"Course completion status response for user [%s] for course_run_ids [%s] is [%s]",
username,
course_run_ids,
course_completion_response,
)
# Yes, This is course_credentials_data. The key is named status but
# it contains all the courses data from credentials.
course_credentials_data = course_completion_response.get("status", [])
if course_credentials_data is not None:
filtered_records = [
course_data["course_run"]["key"]
for course_data in course_credentials_data
if course_data["course_run"]["key"] in course_run_ids
and course_data["status"] == settings.CREDENTIALS_COURSE_COMPLETION_STATE
]
return filtered_records, False
return [], False

View File

@@ -6,7 +6,6 @@ import uuid
from collections import namedtuple
from copy import deepcopy
from unittest import mock
from urllib.parse import urlencode
import ddt
import httpretty
@@ -44,10 +43,8 @@ from openedx.core.djangoapps.programs.utils import (
ProgramDataExtender,
ProgramMarketingDataExtender,
ProgramProgressMeter,
get_buy_subscription_url,
get_certificates,
get_logged_in_program_certificate_url,
get_programs_subscription_data,
is_user_enrolled_in_program_type
)
from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory
@@ -1759,100 +1756,3 @@ class TestProgramEnrollment(SharedModuleStoreTestCase):
)
mock_get_programs_by_type.return_value = [self.program]
assert is_user_enrolled_in_program_type(user=self.user, program_type_slug=self.MICROBACHELORS)
@skip_unless_lms
class TestGetProgramsSubscriptionData(TestCase):
"""
Tests for the get_programs_subscription_data utility function.
"""
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.mock_program_subscription_data = [
{'id': uuid.uuid4(), 'resource_id': uuid.uuid4(),
'resource_type': 'program', 'resource_data': None, 'trial_end': '1970-01-01T00:02:03Z',
'price': '100.00', 'currency': 'USD', 'sub_type': 'stripe', 'identifier': 'dummy_1',
'current_period_end': '1970-01-01T00:02:03Z', 'status': 'active',
'customer': 1, 'subscription_state': 'active'},
{'id': uuid.uuid4(), 'resource_id': uuid.uuid4(),
'resource_type': 'program', 'resource_data': None, 'trial_end': '1970-01-01T03:25:12Z',
'price': '1000.00', 'currency': 'USD', 'sub_type': 'stripe', 'identifier': 'dummy_2',
'current_period_end': '1970-05-23T12:05:21Z', 'status': 'subscription_initiated',
'customer': 1, 'subscription_state': 'notStarted'}
]
@mock.patch(UTILS_MODULE + ".get_subscription_api_client")
@mock.patch(UTILS_MODULE + ".log.info")
def test_get_programs_subscription_data(self, mock_log, mock_get_subscription_api_client):
# mock return values
mock_client = mock.Mock()
mock_get_subscription_api_client.return_value = mock_client
mock_response = {"results": self.mock_program_subscription_data, "next": None}
mock_client.get.return_value = mock.Mock(json=lambda: mock_response, raise_for_status=lambda: None)
# call the function
user = mock.Mock()
result = get_programs_subscription_data(user)
# assert expected behavior
mock_log.assert_called_once_with(f"B2C_SUBSCRIPTIONS: Requesting Program subscription data for user: {user}")
mock_get_subscription_api_client.assert_called_once_with(user)
mock_client.get.assert_called_once_with(settings.SUBSCRIPTIONS_API_PATH, params={"page": 1})
assert result == self.mock_program_subscription_data
@mock.patch(UTILS_MODULE + ".get_subscription_api_client")
@mock.patch(UTILS_MODULE + ".log.info")
def test_get_programs_subscription_data_with_uuid(self, mock_log, mock_get_subscription_api_client):
mock_client = mock.Mock()
mock_get_subscription_api_client.return_value = mock_client
subscription_data = self.mock_program_subscription_data[0]
program_uuid = subscription_data['resource_id']
mock_response = {"results": subscription_data, "next": None}
mock_client.get.return_value = mock.Mock(json=lambda: mock_response, raise_for_status=lambda: None)
user = mock.Mock()
result = get_programs_subscription_data(user, program_uuid=program_uuid)
mock_log.assert_called_once_with(f"B2C_SUBSCRIPTIONS: Requesting Program subscription data for user: {user}"
f" for program_uuid: {str(program_uuid)}")
mock_get_subscription_api_client.assert_called_once_with(user)
mock_client.get.assert_called_once_with(
settings.SUBSCRIPTIONS_API_PATH,
params={
"most_active_and_recent": 'true',
"resource_id": program_uuid,
}
)
assert result == subscription_data
@override_settings(SUBSCRIPTIONS_BUY_SUBSCRIPTION_URL='http://subscription_buy_url/')
@ddt.ddt
class TestBuySubscriptionUrl(TestCase):
"""
Tests for the BuySubscriptionUrl utility function.
"""
@ddt.data(
{
'skus': ['TESTSKU'],
'program_uuid': '12345678-9012-3456-7890-123456789012'
},
{
'skus': ['TESTSKU1', 'TESTSKU2', 'TESTSKU3'],
'program_uuid': '12345678-9012-3456-7890-123456789012'
},
{
'skus': [],
'program_uuid': '12345678-9012-3456-7890-123456789012'
}
)
@ddt.unpack
def test_get_buy_subscription_url(self, skus, program_uuid):
""" Verify the subscription purchase page URL is properly constructed and returned. """
url = get_buy_subscription_url(program_uuid, skus)
formatted_skus = urlencode({'sku': skus}, doseq=True)
expected_url = f'{settings.SUBSCRIPTIONS_BUY_SUBSCRIPTION_URL}{program_uuid}/?{formatted_skus}'
assert url == expected_url

View File

@@ -5,9 +5,8 @@ import logging
from collections import defaultdict
from copy import deepcopy
from itertools import chain
from urllib.parse import urlencode, urljoin, urlparse, urlunparse
from urllib.parse import urljoin, urlparse, urlunparse
import requests
from dateutil.parser import parse
from django.conf import settings
from django.contrib.auth import get_user_model
@@ -15,7 +14,6 @@ from django.contrib.sites.models import Site
from django.core.cache import cache
from django.urls import reverse
from django.utils.functional import cached_property
from edx_rest_api_client.auth import SuppliedJwtAuth
from opaque_keys.edx.keys import CourseKey
from pytz import utc
from requests.exceptions import RequestException
@@ -42,7 +40,6 @@ from openedx.core.djangoapps.content.course_overviews.models import CourseOvervi
from openedx.core.djangoapps.credentials.utils import get_credentials, get_credentials_records_url
from openedx.core.djangoapps.enrollments.api import get_enrollments
from openedx.core.djangoapps.enrollments.permissions import ENROLL_IN_COURSE
from openedx.core.djangoapps.oauth_dispatch.jwt import create_jwt_for_user
from openedx.core.djangoapps.programs import ALWAYS_CALCULATE_PROGRAM_PRICE_AS_ANONYMOUS_USER
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from xmodule.modulestore.django import modulestore
@@ -64,15 +61,6 @@ def get_program_and_course_data(site, user, program_uuid, mobile_only=False):
return program_data, course_data
def get_buy_subscription_url(program_uuid, skus):
"""
Returns the URL to the Subscription Purchase page for the given program UUID and course Skus.
"""
formatted_skus = urlencode({"sku": skus}, doseq=True)
url = f"{settings.SUBSCRIPTIONS_BUY_SUBSCRIPTION_URL}{program_uuid}/?{formatted_skus}"
return url
def get_program_urls(program_data):
"""Returns important urls of program."""
from lms.djangoapps.learner_dashboard.utils import FAKE_COURSE_KEY, strip_course_id
@@ -92,10 +80,6 @@ def get_program_urls(program_data):
"commerce_api_url": reverse("commerce_api:v0:baskets:create"),
"buy_button_url": ecommerce_service.get_checkout_page_url(*skus),
"program_record_url": program_record_url,
"buy_subscription_url": get_buy_subscription_url(program_uuid, skus),
"manage_subscription_url": settings.SUBSCRIPTIONS_MANAGE_SUBSCRIPTION_URL,
"orders_and_subscriptions_url": settings.ORDER_HISTORY_MICROFRONTEND_URL,
"subscriptions_learner_help_center_url": settings.SUBSCRIPTIONS_LEARNER_HELP_CENTER_URL,
}
return urls
@@ -129,15 +113,6 @@ def get_program_marketing_url(programs_config, mobile_only=False):
return marketing_url
def get_program_subscriptions_marketing_url():
"""Build a URL used to link to subscription eligible programs on the marketing site."""
marketing_urls = settings.MKTG_URLS
return urljoin(
marketing_urls.get("ROOT"),
marketing_urls.get("PROGRAM_SUBSCRIPTIONS"),
)
def attach_program_detail_url(programs, mobile_only=False):
"""Extend program representations by attaching a URL to be used when linking to program details.
@@ -1042,51 +1017,3 @@ def is_user_enrolled_in_program_type(
elif course_run_id in course_runs:
return True
return False
def get_subscription_api_client(user):
"""
Returns an API client which can be used to make Subscriptions API requests.
"""
scopes = ["user_id", "email", "profile"]
jwt = create_jwt_for_user(user, scopes=scopes)
client = requests.Session()
client.auth = SuppliedJwtAuth(jwt)
return client
def get_programs_subscription_data(user, program_uuid=None):
"""
Returns the subscription data for a user's program if uuid is specified
else return data for user's all subscriptions.
"""
client = get_subscription_api_client(user)
api_path = f"{settings.SUBSCRIPTIONS_API_PATH}"
subscription_data = []
log.info(
f"B2C_SUBSCRIPTIONS: Requesting Program subscription data for user: {user}"
+ (f" for program_uuid: {program_uuid}" if program_uuid is not None else "")
)
try:
if program_uuid:
response = client.get(api_path, params={"resource_id": program_uuid, "most_active_and_recent": "true"})
response.raise_for_status()
subscription_data = response.json().get("results", [])
else:
next_page = 1
while next_page:
response = client.get(api_path, params=dict(page=next_page))
response.raise_for_status()
subscription_data.extend(response.json().get("results", []))
next_page = response.json().get("next")
except Exception as exc: # pylint: disable=broad-except
log.exception(
f"B2C_SUBSCRIPTIONS: Failed to retrieve Program Subscription Data for user: {user} with error: {exc}"
+ f" for program_uuid: {str(program_uuid)}"
if program_uuid is not None
else ""
)
return subscription_data