Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
70eaf189
Commit
70eaf189
authored
Jan 01, 2016
by
Ahsan Ulhaq
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
caching for requests to credentials service
ECOM-3278
parent
51815136
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
381 additions
and
92 deletions
+381
-92
cms/djangoapps/contentstore/views/tests/test_programs.py
+2
-2
common/djangoapps/student/views.py
+1
-1
lms/urls.py
+2
-0
openedx/core/djangoapps/credentials/migrations/0002_credentialsapiconfig_cache_ttl.py
+19
-0
openedx/core/djangoapps/credentials/models.py
+13
-0
openedx/core/djangoapps/credentials/tests/mixins.py
+4
-3
openedx/core/djangoapps/credentials/tests/test_utils.py
+49
-18
openedx/core/djangoapps/credentials/utils.py
+8
-3
openedx/core/djangoapps/organization_api/__init__.py
+6
-0
openedx/core/djangoapps/organization_api/api/__init__.py
+0
-0
openedx/core/djangoapps/organization_api/api/views.py
+38
-0
openedx/core/djangoapps/organization_api/tests/__init__.py
+0
-0
openedx/core/djangoapps/organization_api/tests/test_views.py
+126
-0
openedx/core/djangoapps/organization_api/urls.py
+19
-0
openedx/core/djangoapps/programs/tests/test_utils.py
+51
-0
openedx/core/djangoapps/programs/utils.py
+6
-4
openedx/core/lib/edx_api_utils.py
+20
-23
openedx/core/lib/tests/test_edx_api_utils.py
+17
-38
No files found.
cms/djangoapps/contentstore/views/tests/test_programs.py
View file @
70eaf189
...
@@ -21,8 +21,6 @@ class TestProgramListing(ProgramsApiConfigMixin, ProgramsDataMixin, SharedModule
...
@@ -21,8 +21,6 @@ class TestProgramListing(ProgramsApiConfigMixin, ProgramsDataMixin, SharedModule
ClientFactory
(
name
=
ProgramsApiConfig
.
OAUTH2_CLIENT_NAME
,
client_type
=
CONFIDENTIAL
)
ClientFactory
(
name
=
ProgramsApiConfig
.
OAUTH2_CLIENT_NAME
,
client_type
=
CONFIDENTIAL
)
self
.
create_programs_config
()
self
.
staff
=
UserFactory
(
is_staff
=
True
)
self
.
staff
=
UserFactory
(
is_staff
=
True
)
self
.
client
.
login
(
username
=
self
.
staff
.
username
,
password
=
'test'
)
self
.
client
.
login
(
username
=
self
.
staff
.
username
,
password
=
'test'
)
...
@@ -50,6 +48,7 @@ class TestProgramListing(ProgramsApiConfigMixin, ProgramsDataMixin, SharedModule
...
@@ -50,6 +48,7 @@ class TestProgramListing(ProgramsApiConfigMixin, ProgramsDataMixin, SharedModule
student
=
UserFactory
(
is_staff
=
False
)
student
=
UserFactory
(
is_staff
=
False
)
self
.
client
.
login
(
username
=
student
.
username
,
password
=
'test'
)
self
.
client
.
login
(
username
=
student
.
username
,
password
=
'test'
)
self
.
create_programs_config
()
self
.
mock_programs_api
()
self
.
mock_programs_api
()
response
=
self
.
client
.
get
(
self
.
studio_home
)
response
=
self
.
client
.
get
(
self
.
studio_home
)
...
@@ -60,6 +59,7 @@ class TestProgramListing(ProgramsApiConfigMixin, ProgramsDataMixin, SharedModule
...
@@ -60,6 +59,7 @@ class TestProgramListing(ProgramsApiConfigMixin, ProgramsDataMixin, SharedModule
"""Verify that the programs tab and creation button can be rendered when config is enabled."""
"""Verify that the programs tab and creation button can be rendered when config is enabled."""
# When no data is provided, expect creation prompt.
# When no data is provided, expect creation prompt.
self
.
create_programs_config
()
self
.
mock_programs_api
(
data
=
{
'results'
:
[]})
self
.
mock_programs_api
(
data
=
{
'results'
:
[]})
response
=
self
.
client
.
get
(
self
.
studio_home
)
response
=
self
.
client
.
get
(
self
.
studio_home
)
...
...
common/djangoapps/student/views.py
View file @
70eaf189
...
@@ -2432,7 +2432,7 @@ def _get_xseries_credentials(user):
...
@@ -2432,7 +2432,7 @@ def _get_xseries_credentials(user):
programs_credentials
=
get_user_program_credentials
(
user
)
programs_credentials
=
get_user_program_credentials
(
user
)
credentials_data
=
[]
credentials_data
=
[]
for
program
in
programs_credentials
:
for
program
in
programs_credentials
:
if
program
.
get
(
'
status'
)
==
'active
'
:
if
program
.
get
(
'
category'
)
==
'xseries
'
:
try
:
try
:
program_data
=
{
program_data
=
{
'display_name'
:
program
[
'name'
],
'display_name'
:
program
[
'name'
],
...
...
lms/urls.py
View file @
70eaf189
...
@@ -102,6 +102,8 @@ urlpatterns = (
...
@@ -102,6 +102,8 @@ urlpatterns = (
url
(
r'^api/commerce/'
,
include
(
'commerce.api.urls'
,
namespace
=
'commerce_api'
)),
url
(
r'^api/commerce/'
,
include
(
'commerce.api.urls'
,
namespace
=
'commerce_api'
)),
url
(
r'^api/credit/'
,
include
(
'openedx.core.djangoapps.credit.urls'
,
app_name
=
"credit"
,
namespace
=
'credit'
)),
url
(
r'^api/credit/'
,
include
(
'openedx.core.djangoapps.credit.urls'
,
app_name
=
"credit"
,
namespace
=
'credit'
)),
url
(
r'^rss_proxy/'
,
include
(
'rss_proxy.urls'
,
namespace
=
'rss_proxy'
)),
url
(
r'^rss_proxy/'
,
include
(
'rss_proxy.urls'
,
namespace
=
'rss_proxy'
)),
url
(
r'^api/organizations/'
,
include
(
'openedx.core.djangoapps.organization_api.urls'
,
app_name
=
'organization_api'
,
namespace
=
'organization_api'
)),
)
)
if
settings
.
FEATURES
[
"ENABLE_COMBINED_LOGIN_REGISTRATION"
]:
if
settings
.
FEATURES
[
"ENABLE_COMBINED_LOGIN_REGISTRATION"
]:
...
...
openedx/core/djangoapps/credentials/migrations/0002_credentialsapiconfig_cache_ttl.py
0 → 100644
View file @
70eaf189
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
django.db
import
migrations
,
models
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'credentials'
,
'0001_initial'
),
]
operations
=
[
migrations
.
AddField
(
model_name
=
'credentialsapiconfig'
,
name
=
'cache_ttl'
,
field
=
models
.
PositiveIntegerField
(
default
=
0
,
help_text
=
'Specified in seconds. Enable caching by setting this to a value greater than 0.'
,
verbose_name
=
'Cache Time To Live'
),
),
]
openedx/core/djangoapps/credentials/models.py
View file @
70eaf189
...
@@ -17,6 +17,7 @@ class CredentialsApiConfig(ConfigurationModel):
...
@@ -17,6 +17,7 @@ class CredentialsApiConfig(ConfigurationModel):
"""
"""
OAUTH2_CLIENT_NAME
=
'credentials'
OAUTH2_CLIENT_NAME
=
'credentials'
API_NAME
=
'credentials'
API_NAME
=
'credentials'
CACHE_KEY
=
'credentials.api.data'
internal_service_url
=
models
.
URLField
(
verbose_name
=
_
(
"Internal Service URL"
))
internal_service_url
=
models
.
URLField
(
verbose_name
=
_
(
"Internal Service URL"
))
public_service_url
=
models
.
URLField
(
verbose_name
=
_
(
"Public Service URL"
))
public_service_url
=
models
.
URLField
(
verbose_name
=
_
(
"Public Service URL"
))
...
@@ -35,6 +36,13 @@ class CredentialsApiConfig(ConfigurationModel):
...
@@ -35,6 +36,13 @@ class CredentialsApiConfig(ConfigurationModel):
"Enable authoring of Credential Service credentials in Studio."
"Enable authoring of Credential Service credentials in Studio."
)
)
)
)
cache_ttl
=
models
.
PositiveIntegerField
(
verbose_name
=
_
(
"Cache Time To Live"
),
default
=
0
,
help_text
=
_
(
"Specified in seconds. Enable caching by setting this to a value greater than 0."
)
)
def
__unicode__
(
self
):
def
__unicode__
(
self
):
return
self
.
public_api_url
return
self
.
public_api_url
...
@@ -67,3 +75,8 @@ class CredentialsApiConfig(ConfigurationModel):
...
@@ -67,3 +75,8 @@ class CredentialsApiConfig(ConfigurationModel):
be enabled or not.
be enabled or not.
"""
"""
return
self
.
enabled
and
self
.
enable_studio_authoring
return
self
.
enabled
and
self
.
enable_studio_authoring
@property
def
is_cache_enabled
(
self
):
"""Whether responses from the Credentials API will be cached."""
return
self
.
cache_ttl
>
0
openedx/core/djangoapps/credentials/tests/mixins.py
View file @
70eaf189
...
@@ -15,6 +15,7 @@ class CredentialsApiConfigMixin(object):
...
@@ -15,6 +15,7 @@ class CredentialsApiConfigMixin(object):
'public_service_url'
:
'http://public.credentials.org/'
,
'public_service_url'
:
'http://public.credentials.org/'
,
'enable_learner_issuance'
:
True
,
'enable_learner_issuance'
:
True
,
'enable_studio_authoring'
:
True
,
'enable_studio_authoring'
:
True
,
'cache_ttl'
:
0
,
}
}
def
create_credentials_config
(
self
,
**
kwargs
):
def
create_credentials_config
(
self
,
**
kwargs
):
...
@@ -99,7 +100,7 @@ class CredentialsDataMixin(object):
...
@@ -99,7 +100,7 @@ class CredentialsDataMixin(object):
}
}
CREDENTIALS_NEXT_API_RESPONSE
=
{
CREDENTIALS_NEXT_API_RESPONSE
=
{
"next"
:
'next_page_url'
,
"next"
:
None
,
"results"
:
[
"results"
:
[
{
{
"id"
:
7
,
"id"
:
7
,
...
@@ -140,9 +141,9 @@ class CredentialsDataMixin(object):
...
@@ -140,9 +141,9 @@ class CredentialsDataMixin(object):
body
=
json
.
dumps
(
data
)
body
=
json
.
dumps
(
data
)
if
is_next_page
:
if
is_next_page
:
next_page_data
=
self
.
CREDENTIALS_NEXT_API_RESPONSE
next_page_body
=
json
.
dumps
(
next_page_data
)
next_page_url
=
internal_api_url
+
'/user_credentials/?page=2&username='
+
user
.
username
next_page_url
=
internal_api_url
+
'/user_credentials/?page=2&username='
+
user
.
username
self
.
CREDENTIALS_NEXT_API_RESPONSE
[
'next'
]
=
next_page_url
next_page_body
=
json
.
dumps
(
self
.
CREDENTIALS_NEXT_API_RESPONSE
)
httpretty
.
register_uri
(
httpretty
.
register_uri
(
httpretty
.
GET
,
next_page_url
,
body
=
body
,
content_type
=
'application/json'
,
status
=
status_code
httpretty
.
GET
,
next_page_url
,
body
=
body
,
content_type
=
'application/json'
,
status
=
status_code
)
)
...
...
openedx/core/djangoapps/credentials/tests/test_utils.py
View file @
70eaf189
"""Tests covering Credentials utilities."""
"""Tests covering Credentials utilities."""
from
django.core.cache
import
cache
from
django.test
import
TestCase
from
django.test
import
TestCase
import
httpretty
import
httpretty
from
oauth2_provider.tests.factories
import
ClientFactory
from
oauth2_provider.tests.factories
import
ClientFactory
...
@@ -26,6 +27,8 @@ class TestCredentialsRetrieval(ProgramsApiConfigMixin, CredentialsApiConfigMixin
...
@@ -26,6 +27,8 @@ class TestCredentialsRetrieval(ProgramsApiConfigMixin, CredentialsApiConfigMixin
ClientFactory
(
name
=
ProgramsApiConfig
.
OAUTH2_CLIENT_NAME
,
client_type
=
CONFIDENTIAL
)
ClientFactory
(
name
=
ProgramsApiConfig
.
OAUTH2_CLIENT_NAME
,
client_type
=
CONFIDENTIAL
)
self
.
user
=
UserFactory
()
self
.
user
=
UserFactory
()
cache
.
clear
()
@httpretty.activate
@httpretty.activate
def
test_get_user_credentials
(
self
):
def
test_get_user_credentials
(
self
):
"""Verify user credentials data can be retrieve."""
"""Verify user credentials data can be retrieve."""
...
@@ -35,6 +38,30 @@ class TestCredentialsRetrieval(ProgramsApiConfigMixin, CredentialsApiConfigMixin
...
@@ -35,6 +38,30 @@ class TestCredentialsRetrieval(ProgramsApiConfigMixin, CredentialsApiConfigMixin
actual
=
get_user_credentials
(
self
.
user
)
actual
=
get_user_credentials
(
self
.
user
)
self
.
assertEqual
(
actual
,
self
.
CREDENTIALS_API_RESPONSE
[
'results'
])
self
.
assertEqual
(
actual
,
self
.
CREDENTIALS_API_RESPONSE
[
'results'
])
@httpretty.activate
def
test_get_user_credentials_caching
(
self
):
"""Verify that when enabled, the cache is used for non-staff users."""
self
.
create_credentials_config
(
cache_ttl
=
1
)
self
.
mock_credentials_api
(
self
.
user
)
# Warm up the cache.
get_user_credentials
(
self
.
user
)
# Hit the cache.
get_user_credentials
(
self
.
user
)
# Verify only one request was made.
self
.
assertEqual
(
len
(
httpretty
.
httpretty
.
latest_requests
),
1
)
staff_user
=
UserFactory
(
is_staff
=
True
)
# Hit the Credentials API twice.
for
_
in
range
(
2
):
get_user_credentials
(
staff_user
)
# Verify that three requests have been made (one for student, two for staff).
self
.
assertEqual
(
len
(
httpretty
.
httpretty
.
latest_requests
),
3
)
def
test_get_user_program_credentials_issuance_disable
(
self
):
def
test_get_user_program_credentials_issuance_disable
(
self
):
"""Verify that user program credentials cannot be retrieved if issuance is disabled."""
"""Verify that user program credentials cannot be retrieved if issuance is disabled."""
self
.
create_credentials_config
(
enable_learner_issuance
=
False
)
self
.
create_credentials_config
(
enable_learner_issuance
=
False
)
...
@@ -50,6 +77,28 @@ class TestCredentialsRetrieval(ProgramsApiConfigMixin, CredentialsApiConfigMixin
...
@@ -50,6 +77,28 @@ class TestCredentialsRetrieval(ProgramsApiConfigMixin, CredentialsApiConfigMixin
self
.
assertEqual
(
actual
,
[])
self
.
assertEqual
(
actual
,
[])
@httpretty.activate
@httpretty.activate
def
test_get_user_programs_credentials
(
self
):
"""Verify program credentials data can be retrieved and parsed correctly."""
# create credentials and program configuration
credentials_config
=
self
.
create_credentials_config
()
self
.
create_programs_config
()
# Mocking the API responses from programs and credentials
self
.
mock_programs_api
()
self
.
mock_credentials_api
(
self
.
user
,
reset_url
=
False
)
actual
=
get_user_program_credentials
(
self
.
user
)
expected
=
self
.
PROGRAMS_API_RESPONSE
[
'results'
]
expected
[
0
][
'credential_url'
]
=
\
credentials_config
.
public_service_url
+
'credentials/'
+
self
.
PROGRAMS_CREDENTIALS_DATA
[
0
][
'uuid'
]
expected
[
1
][
'credential_url'
]
=
\
credentials_config
.
public_service_url
+
'credentials/'
+
self
.
PROGRAMS_CREDENTIALS_DATA
[
1
][
'uuid'
]
# checking response from API is as expected
self
.
assertEqual
(
len
(
actual
),
2
)
self
.
assertEqual
(
actual
,
expected
)
@httpretty.activate
def
test_get_user_program_credentials_revoked
(
self
):
def
test_get_user_program_credentials_revoked
(
self
):
"""Verify behavior if credential revoked."""
"""Verify behavior if credential revoked."""
self
.
create_credentials_config
()
self
.
create_credentials_config
()
...
@@ -68,21 +117,3 @@ class TestCredentialsRetrieval(ProgramsApiConfigMixin, CredentialsApiConfigMixin
...
@@ -68,21 +117,3 @@ class TestCredentialsRetrieval(ProgramsApiConfigMixin, CredentialsApiConfigMixin
self
.
mock_credentials_api
(
self
.
user
,
data
=
credential_data
)
self
.
mock_credentials_api
(
self
.
user
,
data
=
credential_data
)
actual
=
get_user_program_credentials
(
self
.
user
)
actual
=
get_user_program_credentials
(
self
.
user
)
self
.
assertEqual
(
actual
,
[])
self
.
assertEqual
(
actual
,
[])
@httpretty.activate
def
test_get_user_programs_credentials
(
self
):
"""Verify program credentials data can be retrieved and parsed correctly."""
credentials_config
=
self
.
create_credentials_config
()
self
.
create_programs_config
()
self
.
mock_programs_api
()
self
.
mock_credentials_api
(
self
.
user
,
reset_url
=
False
)
actual
=
get_user_program_credentials
(
self
.
user
)
expected
=
self
.
PROGRAMS_API_RESPONSE
[
'results'
]
expected
[
0
][
'credential_url'
]
=
\
credentials_config
.
public_service_url
+
'credentials/'
+
self
.
PROGRAMS_CREDENTIALS_DATA
[
0
][
'uuid'
]
expected
[
1
][
'credential_url'
]
=
\
credentials_config
.
public_service_url
+
'credentials/'
+
self
.
PROGRAMS_CREDENTIALS_DATA
[
1
][
'uuid'
]
self
.
assertEqual
(
len
(
actual
),
2
)
self
.
assertEqual
(
actual
,
expected
)
httpretty
.
reset
()
openedx/core/djangoapps/credentials/utils.py
View file @
70eaf189
...
@@ -4,7 +4,7 @@ import logging
...
@@ -4,7 +4,7 @@ import logging
from
openedx.core.djangoapps.credentials.models
import
CredentialsApiConfig
from
openedx.core.djangoapps.credentials.models
import
CredentialsApiConfig
from
openedx.core.djangoapps.programs.utils
import
get_programs_for_credentials
from
openedx.core.djangoapps.programs.utils
import
get_programs_for_credentials
from
openedx.core.lib.
api_utils
import
get
_api_data
from
openedx.core.lib.
edx_api_utils
import
get_edx
_api_data
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
...
@@ -20,8 +20,13 @@ def get_user_credentials(user):
...
@@ -20,8 +20,13 @@ def get_user_credentials(user):
"""
"""
credential_configuration
=
CredentialsApiConfig
.
current
()
credential_configuration
=
CredentialsApiConfig
.
current
()
user_query
=
{
'username'
:
user
.
username
}
user_query
=
{
'username'
:
user
.
username
}
credentials
=
get_api_data
(
# Bypass caching for staff users, who may be generating credentials and
credential_configuration
,
user
,
credential_configuration
.
API_NAME
,
'user_credentials'
,
querystring
=
user_query
# want to see them displayed immediately.
use_cache
=
credential_configuration
.
is_cache_enabled
and
not
user
.
is_staff
cache_key
=
credential_configuration
.
CACHE_KEY
+
'.'
+
user
.
username
if
use_cache
else
None
credentials
=
get_edx_api_data
(
credential_configuration
,
user
,
'user_credentials'
,
querystring
=
user_query
,
cache_key
=
cache_key
)
)
return
credentials
return
credentials
...
...
openedx/core/djangoapps/organization_api/__init__.py
0 → 100644
View file @
70eaf189
"""
Organization API.
WARNING: This API is intended to move into the 'edx-organizations' repo.
https://github.com/edx/edx-organizations
"""
openedx/core/djangoapps/organization_api/api/__init__.py
0 → 100644
View file @
70eaf189
openedx/core/djangoapps/organization_api/api/views.py
0 → 100644
View file @
70eaf189
"""
Organizations API views.
"""
from
rest_framework
import
permissions
from
rest_framework.authentication
import
SessionAuthentication
from
rest_framework.response
import
Response
from
rest_framework.status
import
HTTP_404_NOT_FOUND
from
rest_framework.views
import
APIView
from
rest_framework_oauth.authentication
import
OAuth2Authentication
from
util.organizations_helpers
import
get_organization_by_short_name
class
OrganizationsView
(
APIView
):
"""
View to get organization information.
"""
authentication_classes
=
(
OAuth2Authentication
,
SessionAuthentication
,)
permission_classes
=
(
permissions
.
IsAuthenticated
,)
def
get
(
self
,
request
,
organization_key
):
"""
Return organization information related to provided organization
key/short_name.
"""
organization
=
get_organization_by_short_name
(
organization_key
)
if
organization
:
logo
=
organization
.
get
(
'logo'
)
organization_data
=
{
'name'
:
organization
.
get
(
'name'
,
''
),
'short_name'
:
organization
.
get
(
'short_name'
,
''
),
'description'
:
organization
.
get
(
'description'
,
''
),
'logo'
:
request
.
build_absolute_uri
(
logo
.
url
)
if
logo
else
''
}
return
Response
(
organization_data
)
return
Response
(
status
=
HTTP_404_NOT_FOUND
)
openedx/core/djangoapps/organization_api/tests/__init__.py
0 → 100644
View file @
70eaf189
openedx/core/djangoapps/organization_api/tests/test_views.py
0 → 100644
View file @
70eaf189
"""
Tests for organization API.
"""
import
ddt
import
json
import
unittest
from
django.conf
import
settings
from
django.core.urlresolvers
import
reverse
,
NoReverseMatch
from
django.test
import
TestCase
from
oauth2_provider.tests.factories
import
AccessTokenFactory
,
ClientFactory
from
student.tests.factories
import
UserFactory
from
util
import
organizations_helpers
@ddt.ddt
@unittest.skipUnless
(
settings
.
ROOT_URLCONF
==
'lms.urls'
,
'Test only valid in lms'
)
class
OrganizationsAPITests
(
TestCase
):
"""
Tests for the organizations API endpoints.
GET /api/organizations/v1/organization/:org_key/
"""
def
setUp
(
self
):
"""
Test setup for the organizations API.
"""
super
(
OrganizationsAPITests
,
self
)
.
setUp
()
self
.
user_password
=
'password'
self
.
user
=
UserFactory
(
password
=
self
.
user_password
)
self
.
test_org_key
=
'test_organization'
self
.
test_org_url
=
self
.
_generate_org_url
(
self
.
test_org_key
)
def
_create_test_organization
(
self
,
org_key
=
None
):
"""
Helper method to create a test organization with the provide 'org_key' and
returns the url to access it.
"""
if
org_key
is
None
:
org_key
=
self
.
test_org_key
test_organization_data
=
{
'name'
:
'Test Organization'
,
'short_name'
:
org_key
,
'description'
:
'Test Organization Description'
,
'logo'
:
'/test_logo.png/'
}
organizations_helpers
.
add_organization
(
organization_data
=
test_organization_data
)
return
self
.
_generate_org_url
(
org_key
)
def
_generate_org_url
(
self
,
org_key
):
"""
Helper method to generate the url to get organization data for a
specific organization key.
"""
return
reverse
(
'organization_api:get_organization'
,
kwargs
=
{
'organization_key'
:
org_key
}
)
def
test_authentication_required
(
self
):
"""
Verify that the endpoint requires authentication.
"""
response
=
self
.
client
.
get
(
self
.
test_org_url
)
self
.
assertEqual
(
response
.
status_code
,
401
)
def
test_session_auth
(
self
):
"""
Verify that the endpoint supports session authentication.
"""
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
self
.
user_password
)
response
=
self
.
client
.
get
(
self
.
test_org_url
)
# verify that the test org does not exist
self
.
assertEqual
(
response
.
status_code
,
404
)
# add a test organization
self
.
_create_test_organization
()
# verify that the organization api return data in correct format
response
=
self
.
client
.
get
(
self
.
test_org_url
)
self
.
assertEqual
(
response
.
status_code
,
200
)
expected_output
=
{
'name'
:
'Test Organization'
,
'short_name'
:
'test_organization'
,
'description'
:
'Test Organization Description'
,
'logo'
:
'http://testserver/test_logo.png/'
}
self
.
assertEqual
(
json
.
loads
(
response
.
content
),
expected_output
)
def
test_oauth
(
self
):
"""
Verify that the organization API supports OAuth.
"""
oauth_client
=
ClientFactory
.
create
()
access_token
=
AccessTokenFactory
.
create
(
user
=
self
.
user
,
client
=
oauth_client
)
.
token
headers
=
{
'HTTP_AUTHORIZATION'
:
'Bearer '
+
access_token
}
response
=
self
.
client
.
get
(
self
.
test_org_url
,
**
headers
)
# verify that the test org does not exist
self
.
assertEqual
(
response
.
status_code
,
404
)
# add a test organization
self
.
_create_test_organization
()
# verify that the organization api return data in correct format
response
=
self
.
client
.
get
(
self
.
test_org_url
,
**
headers
)
self
.
assertEqual
(
response
.
status_code
,
200
)
expected_output
=
{
'name'
:
'Test Organization'
,
'short_name'
:
'test_organization'
,
'description'
:
'Test Organization Description'
,
'logo'
:
'http://testserver/test_logo.png/'
}
self
.
assertEqual
(
json
.
loads
(
response
.
content
),
expected_output
)
@ddt.data
(
"test_org's"
,
"test_org*"
,
"test(org)"
,
"!test"
,
"test org"
)
def
test_with_invalid_org_key
(
self
,
invalid_org_key
):
"""
Verify that organization url does not match for invalid org key.
"""
with
self
.
assertRaises
(
NoReverseMatch
):
self
.
_generate_org_url
(
invalid_org_key
)
openedx/core/djangoapps/organization_api/urls.py
0 → 100644
View file @
70eaf189
"""
URLs for the organization app.
"""
from
django.conf.urls
import
patterns
,
url
from
openedx.core.djangoapps.organization_api.api
import
views
ORGANIZATION_KEY_PATTERN
=
r"(?P<organization_key>((?![\^'\!\(\)\*\s]).)*)"
urlpatterns
=
patterns
(
''
,
url
(
r'^v0/organization/{}/$'
.
format
(
ORGANIZATION_KEY_PATTERN
),
views
.
OrganizationsView
.
as_view
(),
name
=
'get_organization'
),
)
openedx/core/djangoapps/programs/tests/test_utils.py
View file @
70eaf189
...
@@ -2,6 +2,7 @@
...
@@ -2,6 +2,7 @@
from
django.core.cache
import
cache
from
django.core.cache
import
cache
from
django.test
import
TestCase
from
django.test
import
TestCase
import
httpretty
import
httpretty
import
mock
from
oauth2_provider.tests.factories
import
ClientFactory
from
oauth2_provider.tests.factories
import
ClientFactory
from
provider.constants
import
CONFIDENTIAL
from
provider.constants
import
CONFIDENTIAL
...
@@ -41,6 +42,56 @@ class TestProgramRetrieval(ProgramsApiConfigMixin, ProgramsDataMixin,
...
@@ -41,6 +42,56 @@ class TestProgramRetrieval(ProgramsApiConfigMixin, ProgramsDataMixin,
self
.
assertEqual
(
len
(
httpretty
.
httpretty
.
latest_requests
),
1
)
self
.
assertEqual
(
len
(
httpretty
.
httpretty
.
latest_requests
),
1
)
@httpretty.activate
@httpretty.activate
def
test_get_programs_caching
(
self
):
"""Verify that when enabled, the cache is used for non-staff users."""
self
.
create_programs_config
(
cache_ttl
=
1
)
self
.
mock_programs_api
()
# Warm up the cache.
get_programs
(
self
.
user
)
# Hit the cache.
get_programs
(
self
.
user
)
# Verify only one request was made.
self
.
assertEqual
(
len
(
httpretty
.
httpretty
.
latest_requests
),
1
)
staff_user
=
UserFactory
(
is_staff
=
True
)
# Hit the Programs API twice.
for
_
in
range
(
2
):
get_programs
(
staff_user
)
# Verify that three requests have been made (one for student, two for staff).
self
.
assertEqual
(
len
(
httpretty
.
httpretty
.
latest_requests
),
3
)
def
test_get_programs_programs_disabled
(
self
):
"""Verify behavior when programs is disabled."""
self
.
create_programs_config
(
enabled
=
False
)
actual
=
get_programs
(
self
.
user
)
self
.
assertEqual
(
actual
,
[])
@mock.patch
(
'edx_rest_api_client.client.EdxRestApiClient.__init__'
)
def
test_get_programs_client_initialization_failure
(
self
,
mock_init
):
"""Verify behavior when API client fails to initialize."""
self
.
create_programs_config
()
mock_init
.
side_effect
=
Exception
actual
=
get_programs
(
self
.
user
)
self
.
assertEqual
(
actual
,
[])
self
.
assertTrue
(
mock_init
.
called
)
@httpretty.activate
def
test_get_programs_data_retrieval_failure
(
self
):
"""Verify behavior when data can't be retrieved from Programs."""
self
.
create_programs_config
()
self
.
mock_programs_api
(
status_code
=
500
)
actual
=
get_programs
(
self
.
user
)
self
.
assertEqual
(
actual
,
[])
@httpretty.activate
def
test_get_programs_for_dashboard
(
self
):
def
test_get_programs_for_dashboard
(
self
):
"""Verify programs data can be retrieved and parsed correctly."""
"""Verify programs data can be retrieved and parsed correctly."""
self
.
create_programs_config
()
self
.
create_programs_config
()
...
...
openedx/core/djangoapps/programs/utils.py
View file @
70eaf189
...
@@ -4,7 +4,7 @@ from urlparse import urljoin
...
@@ -4,7 +4,7 @@ from urlparse import urljoin
from
openedx.core.djangoapps.credentials.models
import
CredentialsApiConfig
from
openedx.core.djangoapps.credentials.models
import
CredentialsApiConfig
from
openedx.core.djangoapps.programs.models
import
ProgramsApiConfig
from
openedx.core.djangoapps.programs.models
import
ProgramsApiConfig
from
openedx.core.lib.
api_utils
import
get
_api_data
from
openedx.core.lib.
edx_api_utils
import
get_edx
_api_data
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
...
@@ -24,9 +24,11 @@ def get_programs(user):
...
@@ -24,9 +24,11 @@ def get_programs(user):
"""
"""
programs_config
=
ProgramsApiConfig
.
current
()
programs_config
=
ProgramsApiConfig
.
current
()
# Bypass caching for staff users, who may be creating Programs and want to see them displayed immediately.
# Bypass caching for staff users, who may be creating Programs and want
use_cache
=
programs_config
.
is_cache_enabled
and
not
user
.
is_staff
# to see them displayed immediately.
return
get_api_data
(
programs_config
,
user
,
programs_config
.
API_NAME
,
'programs'
,
use_cache
=
use_cache
)
cache_key
=
programs_config
.
CACHE_KEY
if
programs_config
.
is_cache_enabled
and
not
user
.
is_staff
else
None
return
get_edx_api_data
(
programs_config
,
user
,
'programs'
,
cache_key
=
cache_key
)
def
get_programs_for_dashboard
(
user
,
course_keys
):
def
get_programs_for_dashboard
(
user
,
course_keys
):
...
...
openedx/core/lib/api_utils.py
→
openedx/core/lib/
edx_
api_utils.py
View file @
70eaf189
...
@@ -11,19 +11,20 @@ from openedx.core.lib.token_utils import get_id_token
...
@@ -11,19 +11,20 @@ from openedx.core.lib.token_utils import get_id_token
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
def
get_
api_data
(
api_config
,
user
,
api_name
,
resource
,
querystring
=
None
,
use_cache
=
Fals
e
):
def
get_
edx_api_data
(
api_config
,
user
,
resource
,
querystring
=
None
,
cache_key
=
Non
e
):
"""Fetch
the data from the API using provided API Configuration and
"""Fetch
data from an API using provided API configuration and resource
resourc
e.
nam
e.
Arguments:
Arguments:
api_config: The configuration which will be user for requesting data.
api_config (ConfigurationModel): The configuration model governing
interaction with the API.
user (User): The user to authenticate as when requesting data.
user (User): The user to authenticate as when requesting data.
api_name: Name fo the api to be use for logging.
resource(str): Name of the API resource for which data is being
resource: API resource to from where data will be
requested.
requested.
querystring
: Querystring parameters that might be required to request
querystring
(dict): Querystring parameters that might be required to
data.
request
data.
use_cache: Will be used to decide whether to cache the response data
cache_key(str): Where to cache retrieved data. Omitting this will cause the
or not
.
cache to be bypassed
.
Returns:
Returns:
list of dict, representing data returned by the API.
list of dict, representing data returned by the API.
...
@@ -31,23 +32,19 @@ def get_api_data(api_config, user, api_name, resource, querystring=None, use_cac
...
@@ -31,23 +32,19 @@ def get_api_data(api_config, user, api_name, resource, querystring=None, use_cac
no_data
=
[]
no_data
=
[]
if
not
api_config
.
enabled
:
if
not
api_config
.
enabled
:
log
.
warning
(
'
%
s configuration is disabled.'
,
api_
name
)
log
.
warning
(
'
%
s configuration is disabled.'
,
api_
config
.
API_NAME
)
return
no_data
return
no_data
if
use_cache
:
if
cache_key
:
if
api_config
.
CACHE_KEY
:
cached
=
cache
.
get
(
cache_key
)
cached
=
cache
.
get
(
api_config
.
CACHE_KEY
)
if
cached
is
not
None
:
if
cached
is
not
None
:
return
cached
return
cached
else
:
log
.
warning
(
'No cache key available for
%
s configuration.'
,
api_name
)
return
no_data
try
:
try
:
jwt
=
get_id_token
(
user
,
api_config
.
OAUTH2_CLIENT_NAME
)
jwt
=
get_id_token
(
user
,
api_config
.
OAUTH2_CLIENT_NAME
)
api
=
EdxRestApiClient
(
api_config
.
internal_api_url
,
jwt
=
jwt
)
api
=
EdxRestApiClient
(
api_config
.
internal_api_url
,
jwt
=
jwt
)
except
Exception
:
# pylint: disable=broad-except
except
Exception
:
# pylint: disable=broad-except
log
.
exception
(
'Failed to initialize the
%
s API client.'
,
api_
name
)
log
.
exception
(
'Failed to initialize the
%
s API client.'
,
api_
config
.
API_NAME
)
return
no_data
return
no_data
try
:
try
:
...
@@ -63,10 +60,10 @@ def get_api_data(api_config, user, api_name, resource, querystring=None, use_cac
...
@@ -63,10 +60,10 @@ def get_api_data(api_config, user, api_name, resource, querystring=None, use_cac
results
+=
response
.
get
(
'results'
,
no_data
)
results
+=
response
.
get
(
'results'
,
no_data
)
next_page
=
response
.
get
(
'next'
,
None
)
next_page
=
response
.
get
(
'next'
,
None
)
except
Exception
:
# pylint: disable=broad-except
except
Exception
:
# pylint: disable=broad-except
log
.
exception
(
'Failed to retrieve data from the
%
s API.'
,
api_
name
)
log
.
exception
(
'Failed to retrieve data from the
%
s API.'
,
api_
config
.
API_NAME
)
return
no_data
return
no_data
if
use_cache
:
if
cache_key
:
cache
.
set
(
api_config
.
CACHE_KEY
,
results
,
api_config
.
cache_ttl
)
cache
.
set
(
cache_key
,
results
,
api_config
.
cache_ttl
)
return
results
return
results
openedx/core/lib/tests/test_api_utils.py
→
openedx/core/lib/tests/test_
edx_
api_utils.py
View file @
70eaf189
...
@@ -10,13 +10,13 @@ from openedx.core.djangoapps.credentials.models import CredentialsApiConfig
...
@@ -10,13 +10,13 @@ from openedx.core.djangoapps.credentials.models import CredentialsApiConfig
from
openedx.core.djangoapps.credentials.tests.mixins
import
CredentialsApiConfigMixin
,
CredentialsDataMixin
from
openedx.core.djangoapps.credentials.tests.mixins
import
CredentialsApiConfigMixin
,
CredentialsDataMixin
from
openedx.core.djangoapps.programs.models
import
ProgramsApiConfig
from
openedx.core.djangoapps.programs.models
import
ProgramsApiConfig
from
openedx.core.djangoapps.programs.tests.mixins
import
ProgramsApiConfigMixin
,
ProgramsDataMixin
from
openedx.core.djangoapps.programs.tests.mixins
import
ProgramsApiConfigMixin
,
ProgramsDataMixin
from
openedx.core.lib.
api_utils
import
get
_api_data
from
openedx.core.lib.
edx_api_utils
import
get_edx
_api_data
from
student.tests.factories
import
UserFactory
from
student.tests.factories
import
UserFactory
class
TestApiDataRetrieval
(
CredentialsApiConfigMixin
,
CredentialsDataMixin
,
ProgramsApiConfigMixin
,
ProgramsDataMixin
,
class
TestApiDataRetrieval
(
CredentialsApiConfigMixin
,
CredentialsDataMixin
,
ProgramsApiConfigMixin
,
ProgramsDataMixin
,
TestCase
):
TestCase
):
"""Test
data retrieval from the api util function
."""
"""Test
utility for API data retrieval
."""
def
setUp
(
self
):
def
setUp
(
self
):
super
(
TestApiDataRetrieval
,
self
)
.
setUp
()
super
(
TestApiDataRetrieval
,
self
)
.
setUp
()
ClientFactory
(
name
=
CredentialsApiConfig
.
OAUTH2_CLIENT_NAME
,
client_type
=
CONFIDENTIAL
)
ClientFactory
(
name
=
CredentialsApiConfig
.
OAUTH2_CLIENT_NAME
,
client_type
=
CONFIDENTIAL
)
...
@@ -26,12 +26,12 @@ class TestApiDataRetrieval(CredentialsApiConfigMixin, CredentialsDataMixin, Prog
...
@@ -26,12 +26,12 @@ class TestApiDataRetrieval(CredentialsApiConfigMixin, CredentialsDataMixin, Prog
cache
.
clear
()
cache
.
clear
()
@httpretty.activate
@httpretty.activate
def
test_get_api_data_programs
(
self
):
def
test_get_
edx_
api_data_programs
(
self
):
"""Verify programs data can be retrieve
using get
_api_data."""
"""Verify programs data can be retrieve
d using get_edx
_api_data."""
program_config
=
self
.
create_programs_config
()
program_config
=
self
.
create_programs_config
()
self
.
mock_programs_api
()
self
.
mock_programs_api
()
actual
=
get_
api_data
(
program_config
,
self
.
user
,
'programs'
,
'programs'
)
actual
=
get_
edx_api_data
(
program_config
,
self
.
user
,
'programs'
)
self
.
assertEqual
(
self
.
assertEqual
(
actual
,
actual
,
self
.
PROGRAMS_API_RESPONSE
[
'results'
]
self
.
PROGRAMS_API_RESPONSE
[
'results'
]
...
@@ -40,75 +40,54 @@ class TestApiDataRetrieval(CredentialsApiConfigMixin, CredentialsDataMixin, Prog
...
@@ -40,75 +40,54 @@ class TestApiDataRetrieval(CredentialsApiConfigMixin, CredentialsDataMixin, Prog
# Verify the API was actually hit (not the cache).
# Verify the API was actually hit (not the cache).
self
.
assertEqual
(
len
(
httpretty
.
httpretty
.
latest_requests
),
1
)
self
.
assertEqual
(
len
(
httpretty
.
httpretty
.
latest_requests
),
1
)
@httpretty.activate
def
test_get_edx_api_data_disable_config
(
self
):
def
test_get_api_data_credentials
(
self
):
"""Verify no data is retrieved if configuration is disabled."""
"""Verify credentials data can be retrieve using get_api_data."""
credentials_config
=
self
.
create_credentials_config
()
self
.
mock_credentials_api
(
self
.
user
)
querystring
=
{
'username'
:
self
.
user
.
username
}
actual
=
get_api_data
(
credentials_config
,
self
.
user
,
'credentials'
,
'user_credentials'
,
querystring
=
querystring
)
self
.
assertEqual
(
actual
,
self
.
CREDENTIALS_API_RESPONSE
[
'results'
]
)
def
test_get_api_data_disable_config
(
self
):
"""Verify no data is retrieve if configuration is disabled."""
program_config
=
self
.
create_programs_config
(
enabled
=
False
)
program_config
=
self
.
create_programs_config
(
enabled
=
False
)
actual
=
get_
api_data
(
program_config
,
self
.
user
,
'programs'
,
'programs'
)
actual
=
get_
edx_api_data
(
program_config
,
self
.
user
,
'programs'
)
self
.
assertEqual
(
actual
,
[])
self
.
assertEqual
(
actual
,
[])
@httpretty.activate
@httpretty.activate
def
test_get_api_data_cache
(
self
):
def
test_get_
edx_
api_data_cache
(
self
):
"""Verify that when enabled, the cache is used."""
"""Verify that when enabled, the cache is used."""
program_config
=
self
.
create_programs_config
(
cache_ttl
=
1
)
program_config
=
self
.
create_programs_config
(
cache_ttl
=
1
)
self
.
mock_programs_api
()
self
.
mock_programs_api
()
# Warm up the cache.
# Warm up the cache.
get_
api_data
(
program_config
,
self
.
user
,
'programs'
,
'programs'
,
use_cache
=
True
)
get_
edx_api_data
(
program_config
,
self
.
user
,
'programs'
,
cache_key
=
'test.key'
)
# Hit the cache.
# Hit the cache.
get_
api_data
(
program_config
,
self
.
user
,
'programs'
,
'programs'
,
use_cache
=
True
)
get_
edx_api_data
(
program_config
,
self
.
user
,
'programs'
,
cache_key
=
'test.key'
)
# Verify only one request was made.
# Verify only one request was made.
self
.
assertEqual
(
len
(
httpretty
.
httpretty
.
latest_requests
),
1
)
self
.
assertEqual
(
len
(
httpretty
.
httpretty
.
latest_requests
),
1
)
def
test_get_api_data_without_cache_key
(
self
):
"""Verify that when cache enabled without cache key then no data is retrieved."""
ProgramsApiConfig
.
CACHE_KEY
=
None
program_config
=
self
.
create_programs_config
(
cache_ttl
=
1
)
actual
=
get_api_data
(
program_config
,
self
.
user
,
'programs'
,
'programs'
,
use_cache
=
True
)
self
.
assertEqual
(
actual
,
[])
@mock.patch
(
'edx_rest_api_client.client.EdxRestApiClient.__init__'
)
@mock.patch
(
'edx_rest_api_client.client.EdxRestApiClient.__init__'
)
def
test_get_api_data_client_initialization_failure
(
self
,
mock_init
):
def
test_get_
edx_
api_data_client_initialization_failure
(
self
,
mock_init
):
"""Verify behavior when API client fails to initialize."""
"""Verify behavior when API client fails to initialize."""
program_config
=
self
.
create_programs_config
()
program_config
=
self
.
create_programs_config
()
mock_init
.
side_effect
=
Exception
mock_init
.
side_effect
=
Exception
actual
=
get_
api_data
(
program_config
,
self
.
user
,
'programs'
,
'programs'
)
actual
=
get_
edx_api_data
(
program_config
,
self
.
user
,
'programs'
)
self
.
assertEqual
(
actual
,
[])
self
.
assertEqual
(
actual
,
[])
self
.
assertTrue
(
mock_init
.
called
)
self
.
assertTrue
(
mock_init
.
called
)
@httpretty.activate
@httpretty.activate
def
test_get_api_data_retrieval_failure
(
self
):
def
test_get_
edx_
api_data_retrieval_failure
(
self
):
"""Verify behavior when data can't be retrieved from API."""
"""Verify behavior when data can't be retrieved from API."""
program_config
=
self
.
create_programs_config
()
program_config
=
self
.
create_programs_config
()
self
.
mock_programs_api
(
status_code
=
500
)
self
.
mock_programs_api
(
status_code
=
500
)
actual
=
get_
api_data
(
program_config
,
self
.
user
,
'programs'
,
'programs'
)
actual
=
get_
edx_api_data
(
program_config
,
self
.
user
,
'programs'
)
self
.
assertEqual
(
actual
,
[])
self
.
assertEqual
(
actual
,
[])
@httpretty.activate
@httpretty.activate
def
test_get_api_data_multiple_page
(
self
):
def
test_get_
edx_
api_data_multiple_page
(
self
):
"""Verify that all data is retrieve for multiple page response."""
"""Verify that all data is retrieve for multiple page response."""
credentials_config
=
self
.
create_credentials_config
()
credentials_config
=
self
.
create_credentials_config
()
self
.
mock_credentials_api
(
self
.
user
,
is_next_page
=
True
)
self
.
mock_credentials_api
(
self
.
user
,
is_next_page
=
True
)
querystring
=
{
'username'
:
self
.
user
.
username
}
querystring
=
{
'username'
:
self
.
user
.
username
}
actual
=
get_
api_data
(
credentials_config
,
self
.
user
,
'credentials'
,
'user_credentials'
,
querystring
=
querystring
)
actual
=
get_
edx_api_data
(
credentials_config
,
self
.
user
,
'user_credentials'
,
querystring
=
querystring
)
expected_data
=
self
.
CREDENTIALS_NEXT_API_RESPONSE
[
'results'
]
+
self
.
CREDENTIALS_API_RESPONSE
[
'results'
]
expected_data
=
self
.
CREDENTIALS_NEXT_API_RESPONSE
[
'results'
]
+
self
.
CREDENTIALS_API_RESPONSE
[
'results'
]
self
.
assertEqual
(
actual
,
expected_data
)
self
.
assertEqual
(
actual
,
expected_data
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment