Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.git
**/__pycache__
.pytest_cache
.prereqs_cache
**/*.pyc
3 changes: 3 additions & 0 deletions .gcloudignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.git
**/__pycache__
**/*.pyc
16 changes: 11 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ language: python
python:
- "2.7"

services:
- mongodb

node_js: 6

cache:
Expand All @@ -19,11 +22,14 @@ install:
- pip install tox

env:
- TOXENV=pep8
- TOXENV=py27-django111
ARGS="-- paver run_pep8"
- TOXENV=py27-django111
ARGS="-- pytest common/djangoapps/student/tests/test_helpers.py::TestDestroyOAuthTokensHelper"
global:
# Avoid caching edx-platform's entry_points
- TRAVIS_FIXES="pip install -r requirements/edx/local.in"
jobs:
- TOXENV=pep8
- TOXENV=py27-paver-pep8
- TOXENV=py27-studio
- TOXENV=py27-lms

script:
- tox $ARGS
63 changes: 63 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
FROM ubuntu:16.04

RUN rm /bin/sh && ln -s /bin/bash /bin/sh

RUN apt-get update \
&& apt-get upgrade -y \
&& apt-get install -y \
apt-transport-https \
build-essential \
gcc \
g++ \
gettext \
git \
git-core \
gfortran \
golang \
graphviz \
graphviz-dev \
language-pack-en \
libblas-dev \
liblapack-dev \
libatlas-base-dev \
libfreetype6-dev \
libssl-dev \
libffi-dev \
libgeos-dev \
libjpeg8-dev \
libsqlite3-dev \
libmysqlclient-dev \
libpng12-dev \
libpq-dev \
libxml2-dev \
libxmlsec1-dev \
libxslt1-dev \
memcached \
mongodb \
openssl \
pkg-config \
python-apt \
python-dev \
python-mysqldb \
python-cryptography \
python-pip \
python-setuptools \
python-virtualenv \
software-properties-common \
swig \
&& pip install setuptools -U \
&& pip install virtualenv \
&& pip install more-itertools==5.0.0 \
&& pip install tox

# COPY nodesource.gpg.key /tmp/nodesource.gpg.key
# RUN apt-key add /tmp/nodesource.gpg.key \
# && echo 'deb https://deb.nodesource.com/node_8.x xenial main' > /etc/apt/sources.list.d/nodesource.list \
# && echo 'deb-src https://deb.nodesource.com/node_8.x xenial main' >> /etc/apt/sources.list.d/nodesource.list \
# && apt-get update \
# && apt-get install -y nodejs

WORKDIR /app
COPY . /app

ENTRYPOINT ["/app/scripts/docker_tox.sh"]
9 changes: 9 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,12 @@ upgrade: ## update the pip requirements files to use the latest releases satisfy
grep "^django==" requirements/edx/base.txt > requirements/edx/django.txt
sed '/^[dD]jango==/d' requirements/edx/testing.txt > requirements/edx/testing.tmp
mv requirements/edx/testing.tmp requirements/edx/testing.txt

.PHONY: docker-tox
docker-tox:
docker build . -t edx-platform-tox
docker run edx-platform-tox

.PHONY: cloudbuild
cloudbuild:
gcloud builds submit --config cloudbuild.yaml . --project=appsembler-infrastructure
8 changes: 8 additions & 0 deletions cloudbuild.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
steps:
- name: 'gcr.io/cloud-builders/docker'
args: ['build', '-t', 'gcr.io/appsembler-infrastructure/edx-platform-tox', '.']
- name: 'gcr.io/cloud-builders/docker'
args: ['run', 'gcr.io/appsembler-infrastructure/edx-platform-tox']

images: ['gcr.io/appsembler-infrastructure/edx-platform-tox']
timeout: 7200s
14 changes: 13 additions & 1 deletion cms/djangoapps/contentstore/views/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@
from util.date_utils import get_default_time_display
from util.json_request import JsonResponse

from openedx.core.djangoapps.appsembler.api.sites import get_site_for_course


__all__ = ['assets_handler']

REQUEST_DEFAULTS = {
Expand Down Expand Up @@ -572,7 +575,16 @@ def _get_asset_json(display_name, content_type, date, location, thumbnail_locati
Helper method for formatting the asset information to send to client.
'''
asset_url = StaticContent.serialize_asset_key_with_slash(location)
external_url = settings.LMS_BASE + asset_url

domain = settings.LMS_BASE
site_for_course = get_site_for_course(location.course_key)
if site_for_course:
domain = site_for_course.domain

external_url = '//{domain}{asset_url}'.format(
domain=domain,
asset_url=asset_url,
)
return {
'display_name': display_name,
'content_type': content_type,
Expand Down
3 changes: 2 additions & 1 deletion cms/djangoapps/contentstore/views/course.py
Original file line number Diff line number Diff line change
Expand Up @@ -625,7 +625,8 @@ def course_index(request, course_key):
lms_link = get_lms_link_for_item(course_module.location)
reindex_link = None
if settings.FEATURES.get('ENABLE_COURSEWARE_INDEX', False):
reindex_link = "/course/{course_id}/search_reindex".format(course_id=unicode(course_key))
if GlobalStaff().has_user(request.user):
reindex_link = "/course/{course_id}/search_reindex".format(course_id=six.text_type(course_key))
sections = course_module.get_children()
course_structure = _course_outline_json(request, course_module)
locator_to_show = request.GET.get('show', None)
Expand Down
39 changes: 27 additions & 12 deletions cms/djangoapps/contentstore/views/tests/test_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,29 +410,44 @@ class AssetToJsonTestCase(AssetsTestCase):
Unit test for transforming asset information into something
we can send out to the client via JSON.
"""
@override_settings(LMS_BASE="lms_base_url")
def test_basic(self):
upload_date = datetime(2013, 6, 1, 10, 30, tzinfo=UTC)
content_type = 'image/jpg'
course_key = CourseLocator('org', 'class', 'run')
location = course_key.make_asset_key('asset', 'my_file_name.jpg')
thumbnail_location = course_key.make_asset_key('thumbnail', 'my_file_name_thumb.jpg')
upload_date = datetime(2013, 6, 1, 10, 30, tzinfo=UTC)
content_type = 'image/jpg'
course_key = CourseLocator('org', 'class', 'run')
location = course_key.make_asset_key('asset', 'my_file_name.jpg')

def make_asset(self):
thumbnail_location = self.course_key.make_asset_key('thumbnail', 'my_file_name_thumb.jpg')
# pylint: disable=protected-access
output = assets._get_asset_json("my_file", content_type, upload_date, location, thumbnail_location, True)
return assets._get_asset_json(
"my_file",
self.content_type,
self.upload_date,
self.location,
thumbnail_location,
True,
)

@override_settings(LMS_BASE="lms_base_url")
def test_basic(self):
output = self.make_asset()
self.assertEquals(output["display_name"], "my_file")
self.assertEquals(output["date_added"], "Jun 01, 2013 at 10:30 UTC")
self.assertEquals(output["url"], "/asset-v1:org+class+run+type@asset+block@my_file_name.jpg")
self.assertEquals(output["external_url"], "lms_base_url/asset-v1:org+class+run+type@asset+block@my_file_name.jpg")
self.assertEquals(output["external_url"], "//lms_base_url/asset-v1:org+class+run+type@asset+block@my_file_name.jpg")
self.assertEquals(output["portable_url"], "/static/my_file_name.jpg")
self.assertEquals(output["thumbnail"], "/asset-v1:org+class+run+type@thumbnail+block@my_file_name_thumb.jpg")
self.assertEquals(output["id"], unicode(location))
self.assertEquals(output["id"], unicode(self.location))
self.assertEquals(output['locked'], True)

output = assets._get_asset_json("name", content_type, upload_date, location, None, False)
# pylint: disable=protected-access
output = assets._get_asset_json("name", self.content_type, self.upload_date, self.location, None, False)
self.assertIsNone(output["thumbnail"])

@override_settings(LMS_BASE="lms_base_url")
@patch('contentstore.views.assets.get_site_for_course', mock.Mock(return_value=mock.Mock(domain='site_domain')))
def test_site_url(self):
output = self.make_asset()
assert output["external_url"] == "//site_domain/asset-v1:org+class+run+type@asset+block@my_file_name.jpg"


class LockAssetTestCase(AssetsTestCase):
"""
Expand Down
2 changes: 2 additions & 0 deletions common/test/appsembler/customer_themes/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
Empty file.
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
"""
Tests for site configuration's Tahoe customizations.
"""
from mock import patch
import unittest
from os import getenv

from django.test import TestCase
from django.db import IntegrityError, transaction
from django.conf import settings
from django.contrib.sites.models import Site
from django.test import TestCase
from django.test.utils import override_settings

from openedx.core.djangoapps.site_configuration.tests.factories import SiteConfigurationFactory


# TODO: This is an integration test, try to make it less so and more of a unit-test.
@override_settings(
COMPREHENSIVE_THEME_DIRS=['/edx/src/themes'],
COMPREHENSIVE_THEME_DIRS=[settings.REPO_ROOT / 'common/test/appsembler'],
ENABLE_COMPREHENSIVE_THEMING=True,
DEFAULT_SITE_THEME='edx-theme-codebase',
)
Expand Down
65 changes: 54 additions & 11 deletions openedx/core/djangoapps/user_api/accounts/tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import ddt
from django.test import TestCase
from django.test.utils import override_settings
from mock import patch
from mock import patch, Mock

from completion import models
from completion.test_utils import CompletionWaffleTestMixin
Expand All @@ -16,6 +16,8 @@
from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory

from openedx.core.djangolib.testing.utils import FilteredQueryCountMixin

from ..utils import format_social_link, validate_social_link, generate_password


Expand Down Expand Up @@ -65,7 +67,7 @@ def test_social_link_input(self, platform_name, link_input, formatted_link_expec


@ddt.ddt
class CompletionUtilsTestCase(SharedModuleStoreTestCase, CompletionWaffleTestMixin, TestCase):
class CompletionUtilsTestCase(SharedModuleStoreTestCase, FilteredQueryCountMixin, CompletionWaffleTestMixin, TestCase):
"""
Test completion utility functions
"""
Expand Down Expand Up @@ -113,21 +115,35 @@ def submit_faux_completions(self):
)

@override_settings(LMS_ROOT_URL='test_url:9999')
@ddt.data(True, False)
def test_retrieve_last_sitewide_block_completed(self, use_username):
@ddt.unpack
@ddt.data({
'use_username': True,
'engaged_queries': 3,
'cruft_queries': 2,
}, {
'use_username': False,
'engaged_queries': 2,
'cruft_queries': 1,
})
def test_retrieve_last_sitewide_block_completed(self, use_username, engaged_queries, cruft_queries):
"""
Test that the method returns a URL for the "last completed" block
when sending a user object
"""
block_url = retrieve_last_sitewide_block_completed(
self.engaged_user.username if use_username else self.engaged_user
)
empty_block_url = retrieve_last_sitewide_block_completed(
self.cruft_user.username if use_username else self.cruft_user
)
with self.assertNumQueries(engaged_queries):
block_url = retrieve_last_sitewide_block_completed(
self.engaged_user.username if use_username else self.engaged_user
)

with self.assertNumQueries(cruft_queries):
empty_block_url = retrieve_last_sitewide_block_completed(
self.cruft_user.username if use_username else self.cruft_user
)

self.assertEqual(
block_url,
u'test_url:9999/courses/{org}/{course}/{run}/jump_to/i4x://{org}/{course}/vertical/{vertical_id}'.format(
# Appsembler: We're omitting the domain name because our users are always on a single site.
u'/courses/{org}/{course}/{run}/jump_to/i4x://{org}/{course}/vertical/{vertical_id}'.format(
org=self.course.location.course_key.org,
course=self.course.location.course_key.course,
run=self.course.location.course_key.run,
Expand All @@ -136,6 +152,33 @@ def test_retrieve_last_sitewide_block_completed(self, use_username):
)
self.assertEqual(empty_block_url, None)

@override_settings(LMS_ROOT_URL='test_url:9999')
def test_retrieve_last_sitewide_block_performance_with_site(self):
"""
Ensures that the `SiteConfiguration.objects.all()` is not called when a specific site was found.
"""
expected_queries_with_site = 1
with self.assertNumQueries(expected_queries_with_site):
function_path = 'openedx.core.djangoapps.user_api.accounts.utils.get_config_value_from_site_or_settings'
with patch(function_path, Mock(return_value=self.course.location.course_key.org)):
assert retrieve_last_sitewide_block_completed(self.engaged_user).startswith('/')

with self.assertNumQueries(expected_queries_with_site):
assert retrieve_last_sitewide_block_completed(self.cruft_user) is None

@override_settings(LMS_ROOT_URL='test_url:9999')
def test_retrieve_last_sitewide_block_performance_multi_course(self):
"""
Ensures that the `SiteConfiguration.objects.all()` is called only once when no site was found.
"""
self.course = self.create_test_course() # create another course.
self.submit_faux_completions() # Test submission for another course
expected_queries_mutli_course_site_wide = 2
with self.assertNumQueries(expected_queries_mutli_course_site_wide):
function_path = 'openedx.core.djangoapps.user_api.accounts.utils.get_config_value_from_site_or_settings'
with patch(function_path, Mock(return_value=None)): # Pretend that no sites are matching the courses
assert retrieve_last_sitewide_block_completed(self.engaged_user).startswith('/')


class GeneratePasswordTest(TestCase):
"""Tests formation of randomly generated passwords."""
Expand Down
Loading