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

98 statements  

« prev     ^ index     » next       coverage.py v7.6.4, created at 2024-10-21 19:31 +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 event = models.EnrollmentEvent.objects.create( 

75 transit_agency=agency, 

76 enrollment_flow=flow, 

77 enrollment_method=models.EnrollmentMethods.DIGITAL, 

78 verified_by=flow.eligibility_verifier, 

79 expiration_datetime=expiry, 

80 ) 

81 event.save() 

82 analytics.returned_success(request, flow.group_id) 

83 return success(request) 

84 

85 case Status.SYSTEM_ERROR: 

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

87 sentry_sdk.capture_exception(exception) 

88 return system_error(request) 

89 

90 case Status.EXCEPTION: 

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

92 raise exception 

93 

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

95 return reenrollment_error(request) 

96 

97 # GET enrollment index 

98 else: 

99 agency = session.agency(request) 

100 flow = session.flow(request) 

101 

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

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

104 tokenize_system_error_form = forms.CardTokenizeFailForm( 

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

106 ) 

107 tokenize_success_form = forms.CardTokenizeSuccessForm( 

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

109 ) 

110 

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

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

113 

114 context = { 

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

116 "cta_button": "tokenize_card", 

117 "card_tokenize_env": agency.transit_processor.card_tokenize_env, 

118 "card_tokenize_func": agency.transit_processor.card_tokenize_func, 

119 "card_tokenize_url": agency.transit_processor.card_tokenize_url, 

120 "enrollment_method": models.EnrollmentMethods.DIGITAL, 

121 "token_field": "card_token", 

122 "form_retry": tokenize_retry_form.id, 

123 "form_server_error": tokenize_server_error_form.id, 

124 "form_success": tokenize_success_form.id, 

125 "form_system_error": tokenize_system_error_form.id, 

126 "overlay_language": overlay_language, 

127 } 

128 

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

130 

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

132 

133 

134@decorator_from_middleware(EligibleSessionRequired) 

135def reenrollment_error(request): 

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

137 flow = session.flow(request) 

138 

139 if flow.reenrollment_error_template is None: 

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

141 

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

143 # overwrite origin for a logged in user 

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

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

146 

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

148 

149 return TemplateResponse(request, flow.reenrollment_error_template) 

150 

151 

152@decorator_from_middleware(EligibleSessionRequired) 

153def retry(request): 

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

155 analytics.returned_retry(request) 

156 return TemplateResponse(request, TEMPLATE_RETRY) 

157 

158 

159@decorator_from_middleware(EligibleSessionRequired) 

160def system_error(request): 

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

162 

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

164 agency = session.agency(request) 

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

166 

167 return TemplateResponse(request, TEMPLATE_SYSTEM_ERROR) 

168 

169 

170@pageview_decorator 

171@decorator_from_middleware(EligibleSessionRequired) 

172@decorator_from_middleware(FlowSessionRequired) 

173def success(request): 

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

175 request.path = "/enrollment/success" 

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

177 

178 flow = session.flow(request) 

179 

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

181 # overwrite origin for a logged in user 

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

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

184 

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

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