Merge pull request #32928 from openedx/sr-pact-provider
feat: Pact Provider Verification for Profile Endpoint
This commit is contained in:
@@ -11,4 +11,7 @@ PROVIDER_STATES_URL = True
|
||||
MOCK_USERNAME = 'Mock User'
|
||||
|
||||
######################### Add Authentication Middleware for Pact Verification Calls #########################
|
||||
MIDDLEWARE = MIDDLEWARE + ['common.test.pacts.middleware.AuthenticationMiddleware', ]
|
||||
MIDDLEWARE = MIDDLEWARE + [
|
||||
'common.test.pacts.middleware.AuthenticationMiddleware',
|
||||
'openedx.core.djangoapps.user_api.accounts.tests.pact.user-middleware',
|
||||
]
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"consumer": {
|
||||
"name": "frontend-app-profile"
|
||||
},
|
||||
"provider": {
|
||||
"name": "edx-platform"
|
||||
},
|
||||
"interactions": [
|
||||
{
|
||||
"description": "A request for user's basic information",
|
||||
"providerState": "I have a user's basic information",
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"path": "/api/user/v1/accounts/staff"
|
||||
},
|
||||
"response": {
|
||||
"body": {
|
||||
"bio": "This is my bio",
|
||||
"country": "ME",
|
||||
"name": "Lemon Seltzer",
|
||||
"username": "staff",
|
||||
"is_active": true,
|
||||
"gender": "m",
|
||||
"mailing_address": "Park Ave",
|
||||
"goals": "Learn and Grow!",
|
||||
"year_of_birth": 1901,
|
||||
"phone_number": "+11234567890"
|
||||
},
|
||||
"headers": {"Content-Type": "application/json"},
|
||||
"matchingRules": {
|
||||
"$.body.bio": {
|
||||
"match": "type"
|
||||
},
|
||||
"$.body.country": {
|
||||
"match": "type"
|
||||
},
|
||||
"$.body.name": {
|
||||
"match": "type"
|
||||
},
|
||||
"$.body.username": {
|
||||
"match": "type"
|
||||
},
|
||||
"$.body.is_active": {
|
||||
"match": "type"
|
||||
},
|
||||
"$.body.gender": {
|
||||
"match": "type"
|
||||
},
|
||||
"$.body.mailing_address": {
|
||||
"match": "type"
|
||||
},
|
||||
"$.body.goals": {
|
||||
"match": "type"
|
||||
},
|
||||
"$.body.year_of_birth": {
|
||||
"match": "type"
|
||||
},
|
||||
"$.body.phone_number": {
|
||||
"match": "type"
|
||||
},
|
||||
"status": 200
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"pact-js": {
|
||||
"version": "11.0.2"
|
||||
},
|
||||
"pactRust": {
|
||||
"ffi": "0.4.0",
|
||||
"models": "1.0.4"
|
||||
},
|
||||
"pactSpecification": {
|
||||
"version": "2.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
"""
|
||||
Contain the middleware logic needed during pact verification
|
||||
"""
|
||||
from django.contrib import auth
|
||||
from django.utils.deprecation import MiddlewareMixin
|
||||
|
||||
User = auth.get_user_model()
|
||||
|
||||
|
||||
class AuthenticationMiddleware(MiddlewareMixin):
|
||||
"""
|
||||
Middleware to add default authentication into the requests for pact verification.
|
||||
|
||||
This middleware is required to add a default authenticated user and bypass CSRF validation
|
||||
into the requests during the pact verification workflow. Without the authentication, the pact verification
|
||||
process will not work as the apis.
|
||||
See https://docs.pact.io/faq#how-do-i-test-oauth-or-other-security-headers
|
||||
"""
|
||||
|
||||
def __init__(self, get_response):
|
||||
super().__init__(get_response)
|
||||
self.auth_user = User.objects.get_or_create(username='staff', is_staff=True)[0]
|
||||
self.get_response = get_response
|
||||
|
||||
def process_view(self, request, view_func, view_args, view_kwargs): # pylint: disable=unused-argument
|
||||
"""
|
||||
Add a default authenticated user and remove CSRF checks for a request
|
||||
in a subset of views.
|
||||
"""
|
||||
if request.user.is_anonymous and 'Pact-Authentication' in request.headers:
|
||||
request.user = self.auth_user
|
||||
request._dont_enforce_csrf_checks = True # pylint: disable=protected-access
|
||||
@@ -0,0 +1,79 @@
|
||||
"""
|
||||
User Verification Server for Profile Information
|
||||
"""
|
||||
import os
|
||||
import logging
|
||||
from django.test import LiveServerTestCase
|
||||
from django.urls import reverse
|
||||
from pact import Verifier
|
||||
|
||||
from common.djangoapps.student.tests.factories import UserFactory
|
||||
from common.djangoapps.student.models import User
|
||||
from django.http import JsonResponse
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.views.decorators.http import require_POST
|
||||
import json
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
PACT_DIR = os.path.dirname(os.path.realpath(__file__))
|
||||
PACT_FILE = "frontend-app-profile-edx-platform.json"
|
||||
|
||||
|
||||
class ProviderState():
|
||||
""" Provider State for the testing profile """
|
||||
|
||||
def account_setup(self, request):
|
||||
""" Sets up the Profile that we want to mock in accordance to our contract """
|
||||
User.objects.filter(username="staff").delete()
|
||||
user_acc = UserFactory.create(username="staff")
|
||||
user_acc.profile.name = "Lemon Seltzer"
|
||||
user_acc.profile.bio = "This is my bio"
|
||||
user_acc.profile.country = "ME"
|
||||
user_acc.profile.is_active = True
|
||||
user_acc.profile.goals = "Learn and Grow!"
|
||||
user_acc.profile.year_of_birth = 1901
|
||||
user_acc.profile.phone_number = "+11234567890"
|
||||
user_acc.profile.mailing_address = "Park Ave"
|
||||
user_acc.profile.save()
|
||||
return user_acc
|
||||
|
||||
|
||||
@csrf_exempt
|
||||
@require_POST
|
||||
def provider_state(request):
|
||||
""" Provider State view for our verifier"""
|
||||
state_setup = {"I have a user's basic information": ProviderState().account_setup}
|
||||
request_body = json.loads(request.body)
|
||||
state = request_body.get('state')
|
||||
User.objects.filter(username="staff").delete()
|
||||
print('Setting up provider state for state value: {}'.format(state))
|
||||
state_setup["I have a user's basic information"](request)
|
||||
return JsonResponse({'result': state})
|
||||
|
||||
|
||||
class ProviderVerificationServer(LiveServerTestCase):
|
||||
""" Live Server for Pact Account Verification """
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.PACT_URL = cls.live_server_url
|
||||
|
||||
cls.verifier = Verifier(
|
||||
provider='edx-platform',
|
||||
provider_base_url=cls.PACT_URL,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
super().tearDownClass()
|
||||
|
||||
def test_pact(self):
|
||||
output, _ = self.verifier.verify_pacts(
|
||||
os.path.join(PACT_DIR, PACT_FILE),
|
||||
headers=['Pact-Authentication: Allow', ],
|
||||
provider_states_setup_url=f"{self.PACT_URL}{reverse('acc-provider-state-view')}",
|
||||
)
|
||||
assert output == 0
|
||||
@@ -5,6 +5,7 @@ Defines the URL routes for this app.
|
||||
|
||||
from django.conf import settings
|
||||
from django.urls import include, path, re_path
|
||||
from django.conf.urls import url
|
||||
from rest_framework import routers
|
||||
|
||||
from ..profile_images.views import ProfileImageView
|
||||
@@ -226,3 +227,14 @@ urlpatterns = [
|
||||
path('v1/preferences/time_zones/', user_api_views.CountryTimeZoneListView.as_view(),
|
||||
),
|
||||
]
|
||||
|
||||
# Provider States url for Account
|
||||
if getattr(settings, 'PROVIDER_STATES_URL', None):
|
||||
from openedx.core.djangoapps.user_api.accounts.tests.pact.verify_user import provider_state as acc_provider_state
|
||||
urlpatterns += [
|
||||
url(
|
||||
r'^pact/provider_states/$',
|
||||
acc_provider_state,
|
||||
name='acc-provider-state-view',
|
||||
)
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user