Coverage for benefits/enrollment/views.py: 95%

102 statements  

« prev     ^ index     » next       coverage.py v7.6.7, created at 2024-11-22 18:00 +0000

1""" 

2The enrollment application: view definitions for the benefits enrollment flow. 

3""" 

4 

5import logging 

6 

7 

8from django.http import JsonResponse 

9from django.template.response import TemplateResponse 

10from django.urls import reverse 

11from django.utils.decorators import decorator_from_middleware 

12import sentry_sdk 

13 

14from benefits.routes import routes 

15from benefits.core import session 

16from benefits.core.middleware import EligibleSessionRequired, FlowSessionRequired, pageview_decorator 

17 

18from benefits.core import models 

19from . import analytics, forms 

20from .enrollment import Status, request_card_tokenization_access, enroll 

21 

22TEMPLATE_RETRY = "enrollment/retry.html" 

23TEMPLATE_SYSTEM_ERROR = "enrollment/system_error.html" 

24 

25 

26logger = logging.getLogger(__name__) 

27 

28 

29@decorator_from_middleware(EligibleSessionRequired) 

30def token(request): 

31 """View handler for the enrollment auth token.""" 

32 if not session.enrollment_token_valid(request): 

33 response = request_card_tokenization_access(request) 

34 

35 if response.status is Status.SUCCESS: 

36 session.update(request, enrollment_token=response.access_token, enrollment_token_exp=response.expires_at) 

37 elif response.status is Status.SYSTEM_ERROR or response.status is Status.EXCEPTION: 37 ↛ 50line 37 didn't jump to line 50 because the condition on line 37 was always true

38 logger.debug("Error occurred while requesting access token", exc_info=response.exception) 

39 sentry_sdk.capture_exception(response.exception) 

40 analytics.failed_access_token_request(request, response.status_code) 

41 

42 if response.status is Status.SYSTEM_ERROR: 

43 redirect = reverse(routes.ENROLLMENT_SYSTEM_ERROR) 

44 else: 

45 redirect = reverse(routes.SERVER_ERROR) 

46 

47 data = {"redirect": redirect} 

48 return JsonResponse(data) 

49 

50 data = {"token": session.enrollment_token(request)} 

51 

52 return JsonResponse(data) 

53 

54 

55@decorator_from_middleware(EligibleSessionRequired) 

56def index(request): 

57 """View handler for the enrollment landing page.""" 

58 session.update(request, origin=reverse(routes.ENROLLMENT_INDEX)) 

59 

60 # POST back after transit processor form, process card token 

61 if request.method == "POST": 

62 form = forms.CardTokenizeSuccessForm(request.POST) 

63 if not form.is_valid(): 

64 raise Exception("Invalid card token form") 

65 

66 card_token = form.cleaned_data.get("card_token") 

67 status, exception = enroll(request, card_token) 

68 

69 match (status): 

70 case Status.SUCCESS: 

71 agency = session.agency(request) 

72 flow = session.flow(request) 

73 expiry = session.enrollment_expiry(request) 

74 oauth_extra_claims = session.oauth_extra_claims(request) 

75 # EnrollmentEvent expects a string value for extra_claims 

76 if oauth_extra_claims: 76 ↛ 79line 76 didn't jump to line 79 because the condition on line 76 was always true

77 str_extra_claims = ", ".join(oauth_extra_claims) 

78 else: 

79 str_extra_claims = "" 

80 event = models.EnrollmentEvent.objects.create( 

81 transit_agency=agency, 

82 enrollment_flow=flow, 

83 enrollment_method=models.EnrollmentMethods.DIGITAL, 

84 verified_by=flow.eligibility_verifier, 

85 expiration_datetime=expiry, 

86 extra_claims=str_extra_claims, 

87 ) 

88 event.save() 

89 analytics.returned_success(request, flow.group_id, extra_claims=oauth_extra_claims) 

90 return success(request) 

91 

92 case Status.SYSTEM_ERROR: 

93 analytics.returned_error(request, str(exception)) 

94 sentry_sdk.capture_exception(exception) 

95 return system_error(request) 

96 

97 case Status.EXCEPTION: 

98 analytics.returned_error(request, str(exception)) 

99 raise exception 

100 

101 case Status.REENROLLMENT_ERROR: 101 ↛ exitline 101 didn't return from function 'index' because the pattern on line 101 always matched

102 return reenrollment_error(request) 

103 

104 # GET enrollment index 

105 else: 

106 agency = session.agency(request) 

107 flow = session.flow(request) 

108 

109 tokenize_retry_form = forms.CardTokenizeFailForm(routes.ENROLLMENT_RETRY, "form-card-tokenize-fail-retry") 

110 tokenize_server_error_form = forms.CardTokenizeFailForm(routes.SERVER_ERROR, "form-card-tokenize-fail-server-error") 

111 tokenize_system_error_form = forms.CardTokenizeFailForm( 

112 routes.ENROLLMENT_SYSTEM_ERROR, "form-card-tokenize-fail-system-error" 

113 ) 

114 tokenize_success_form = forms.CardTokenizeSuccessForm( 

115 action_url=routes.ENROLLMENT_INDEX, auto_id=True, label_suffix="" 

116 ) 

117 

118 # mapping from Django's I18N LANGUAGE_CODE to Littlepay's overlay language code 

119 overlay_language = {"en": "en", "es": "es-419"}.get(request.LANGUAGE_CODE, "en") 

120 

121 context = { 

122 "forms": [tokenize_retry_form, tokenize_server_error_form, tokenize_system_error_form, tokenize_success_form], 

123 "cta_button": "tokenize_card", 

124 "card_tokenize_env": agency.transit_processor.card_tokenize_env, 

125 "card_tokenize_func": agency.transit_processor.card_tokenize_func, 

126 "card_tokenize_url": agency.transit_processor.card_tokenize_url, 

127 "enrollment_method": models.EnrollmentMethods.DIGITAL, 

128 "token_field": "card_token", 

129 "form_retry": tokenize_retry_form.id, 

130 "form_server_error": tokenize_server_error_form.id, 

131 "form_success": tokenize_success_form.id, 

132 "form_system_error": tokenize_system_error_form.id, 

133 "overlay_language": overlay_language, 

134 } 

135 

136 logger.debug(f'card_tokenize_url: {context["card_tokenize_url"]}') 

137 

138 return TemplateResponse(request, flow.enrollment_index_template, context) 

139 

140 

141@decorator_from_middleware(EligibleSessionRequired) 

142def reenrollment_error(request): 

143 """View handler for a re-enrollment attempt that is not yet within the re-enrollment window.""" 

144 flow = session.flow(request) 

145 

146 if flow.reenrollment_error_template is None: 

147 raise Exception(f"Re-enrollment error with null template on: {flow}") 

148 

149 if session.logged_in(request) and flow.claims_provider.supports_sign_out: 149 ↛ 152line 149 didn't jump to line 152 because the condition on line 149 was never true

150 # overwrite origin for a logged in user 

151 # if they click the logout button, they are taken to the new route 

152 session.update(request, origin=reverse(routes.LOGGED_OUT)) 

153 

154 analytics.returned_error(request, "Re-enrollment error.") 

155 

156 return TemplateResponse(request, flow.reenrollment_error_template) 

157 

158 

159@decorator_from_middleware(EligibleSessionRequired) 

160def retry(request): 

161 """View handler for a recoverable failure condition.""" 

162 analytics.returned_retry(request) 

163 return TemplateResponse(request, TEMPLATE_RETRY) 

164 

165 

166@decorator_from_middleware(EligibleSessionRequired) 

167def system_error(request): 

168 """View handler for an enrollment system error.""" 

169 

170 # overwrite origin so that CTA takes user to agency index 

171 agency = session.agency(request) 

172 session.update(request, origin=agency.index_url) 

173 

174 return TemplateResponse(request, TEMPLATE_SYSTEM_ERROR) 

175 

176 

177@pageview_decorator 

178@decorator_from_middleware(EligibleSessionRequired) 

179@decorator_from_middleware(FlowSessionRequired) 

180def success(request): 

181 """View handler for the final success page.""" 

182 request.path = "/enrollment/success" 

183 session.update(request, origin=reverse(routes.ENROLLMENT_SUCCESS)) 

184 

185 flow = session.flow(request) 

186 

187 if session.logged_in(request) and flow.claims_provider.supports_sign_out: 

188 # overwrite origin for a logged in user 

189 # if they click the logout button, they are taken to the new route 

190 session.update(request, origin=reverse(routes.LOGGED_OUT)) 

191 

192 context = {"redirect_to": request.path} 

193 return TemplateResponse(request, flow.enrollment_success_template, context)