diff --git a/cms/djangoapps/contentstore/features/transcripts.feature b/cms/djangoapps/contentstore/features/transcripts.feature
index bf5022dd8a..9ca8f349ab 100644
--- a/cms/djangoapps/contentstore/features/transcripts.feature
+++ b/cms/djangoapps/contentstore/features/transcripts.feature
@@ -1,3 +1,4 @@
+@shard_3
Feature: Video Component Editor
As a course author, I want to be able to create video components.
@@ -668,7 +669,7 @@ Feature: Video Component Editor
And I edit the component
And I open tab "Advanced"
- And I revert the transcript field"HTML5 Transcript"
+ And I revert the transcript field "HTML5 Transcript"
And I save changes
Then when I view the video it does not show the captions
diff --git a/cms/djangoapps/contentstore/features/transcripts.py b/cms/djangoapps/contentstore/features/transcripts.py
index c6060803ba..58f0cd6067 100644
--- a/cms/djangoapps/contentstore/features/transcripts.py
+++ b/cms/djangoapps/contentstore/features/transcripts.py
@@ -49,25 +49,18 @@ TRANSCRIPTS_BUTTONS = {
}
-def _clear_field(index):
- world.css_fill(SELECTORS['url_inputs'], '', index)
- # In some reason chromeDriver doesn't trigger 'input' event after filling
- # field by an empty value. That's why we trigger it manually via jQuery.
- world.trigger_event(SELECTORS['url_inputs'], event='input', index=index)
-
-
@step('I clear fields$')
def clear_fields(_step):
- js_str = '''
+
+ # Clear the input fields and trigger an 'input' event
+ script = """
$('{selector}')
- .eq({index})
.prop('disabled', false)
- .removeClass('is-disabled');
- '''
- for index in range(1, 4):
- js = js_str.format(selector=SELECTORS['url_inputs'], index=index - 1)
- world.browser.execute_script(js)
- _clear_field(index)
+ .removeClass('is-disabled')
+ .val('')
+ .trigger('input');
+ """.format(selector=SELECTORS['url_inputs'])
+ world.browser.execute_script(script)
world.wait(DELAY)
world.wait_for_ajax_complete()
@@ -76,7 +69,12 @@ def clear_fields(_step):
@step('I clear field number (.+)$')
def clear_field(_step, index):
index = int(index) - 1
- _clear_field(index)
+ world.css_fill(SELECTORS['url_inputs'], '', index)
+
+ # For some reason ChromeDriver doesn't trigger an 'input' event after filling
+ # the field with an empty value. That's why we trigger it manually via jQuery.
+ world.trigger_event(SELECTORS['url_inputs'], event='input', index=index)
+
world.wait(DELAY)
world.wait_for_ajax_complete()
diff --git a/cms/djangoapps/contentstore/features/video.py b/cms/djangoapps/contentstore/features/video.py
index c97dba10b9..a293b13727 100644
--- a/cms/djangoapps/contentstore/features/video.py
+++ b/cms/djangoapps/contentstore/features/video.py
@@ -34,7 +34,7 @@ def i_created_a_video_component(step):
world.wait_for_present('.is-initialized')
world.wait(DELAY)
- assert not world.css_visible(SELECTORS['spinner'])
+ world.wait_for_invisible(SELECTORS['spinner'])
@step('I have created a Video component with subtitles$')
@@ -59,8 +59,7 @@ def i_created_a_video_with_subs_with_name(_step, sub_id):
world.disable_jquery_animations()
world.wait_for_present('.is-initialized')
- world.wait(DELAY)
- assert not world.css_visible(SELECTORS['spinner'])
+ world.wait_for_invisible(SELECTORS['spinner'])
@step('I have uploaded subtitles "([^"]*)"$')
diff --git a/cms/djangoapps/contentstore/features/youtube_setup.py b/cms/djangoapps/contentstore/features/youtube_setup.py
deleted file mode 100644
index 875b9f2286..0000000000
--- a/cms/djangoapps/contentstore/features/youtube_setup.py
+++ /dev/null
@@ -1,45 +0,0 @@
-#pylint: disable=C0111
-#pylint: disable=W0621
-from xmodule.util.mock_youtube_server.mock_youtube_server import MockYoutubeServer
-from lettuce import before, after, world
-from django.conf import settings
-import threading
-
-from logging import getLogger
-logger = getLogger(__name__)
-
-
-@before.all
-def setup_mock_youtube_server():
- server_host = '127.0.0.1'
-
- server_port = settings.VIDEO_PORT
-
- address = (server_host, server_port)
-
- # Create the mock server instance
- server = MockYoutubeServer(address)
- logger.debug("Youtube server started at {} port".format(str(server_port)))
-
- server.time_to_response = 0.1 # seconds
-
- server.address = address
-
- # Start the server running in a separate daemon thread
- # Because the thread is a daemon, it will terminate
- # when the main thread terminates.
- server_thread = threading.Thread(target=server.serve_forever)
- server_thread.daemon = True
- server_thread.start()
-
- # Store the server instance in lettuce's world
- # so that other steps can access it
- # (and we can shut it down later)
- world.youtube_server = server
-
-
-@after.all
-def teardown_mock_youtube_server(total):
-
- # Stop the LTI server and free up the port
- world.youtube_server.shutdown()
diff --git a/cms/envs/acceptance.py b/cms/envs/acceptance.py
index 67ecfa5689..b47596c9c5 100644
--- a/cms/envs/acceptance.py
+++ b/cms/envs/acceptance.py
@@ -114,22 +114,6 @@ try:
except ImportError:
pass
-# Because an override for where to run will affect which ports to use,
-# set this up after the local overrides.
-if LETTUCE_SELENIUM_CLIENT == 'saucelabs':
- LETTUCE_SERVER_PORT = choice(PORTS)
-else:
- LETTUCE_SERVER_PORT = randint(1024, 65535)
-
-
-# Set up Video information so that the cms will send
-# requests to a mock Youtube server running locally
-if LETTUCE_SELENIUM_CLIENT == 'saucelabs':
- VIDEO_PORT = choice(PORTS)
- PORTS.remove(VIDEO_PORT)
-else:
- VIDEO_PORT = randint(1024, 65535)
-
-# for testing Youtube
-YOUTUBE_API['url'] = "http://127.0.0.1:" + str(VIDEO_PORT) + '/test_transcripts_youtube/'
-
+# Point the URL used to test YouTube availability to our stub YouTube server
+YOUTUBE_TEST_URL = "http://127.0.0.1:{0}/test_youtube/".format(YOUTUBE_PORT)
+YOUTUBE_API['url'] = "http://127.0.0.1:{0}/test_transcripts_youtube/".format(YOUTUBE_PORT)
diff --git a/cms/envs/common.py b/cms/envs/common.py
index daea3a3f0e..d4513900e6 100644
--- a/cms/envs/common.py
+++ b/cms/envs/common.py
@@ -359,6 +359,13 @@ CELERY_QUEUES = {
DEFAULT_PRIORITY_QUEUE: {}
}
+
+############################## Video ##########################################
+
+# URL to test YouTube availability
+YOUTUBE_TEST_URL = 'https://gdata.youtube.com/feeds/api/videos/'
+
+
############################ APPS #####################################
INSTALLED_APPS = (
diff --git a/cms/envs/test.py b/cms/envs/test.py
index a35824ed52..24f9e08a0a 100644
--- a/cms/envs/test.py
+++ b/cms/envs/test.py
@@ -150,6 +150,17 @@ CELERY_ALWAYS_EAGER = True
CELERY_RESULT_BACKEND = 'cache'
BROKER_TRANSPORT = 'memory'
+
+########################### Server Ports ###################################
+
+# These ports are carefully chosen so that if the browser needs to
+# access them, they will be available through the SauceLabs SSH tunnel
+LETTUCE_SERVER_PORT = 8003
+XQUEUE_PORT = 8040
+YOUTUBE_PORT = 8031
+LTI_PORT = 8765
+
+
################### Make tests faster
# http://slacy.com/blog/2012/04/make-your-tests-faster-in-django-1-4/
PASSWORD_HASHERS = (
diff --git a/common/djangoapps/terrain/__init__.py b/common/djangoapps/terrain/__init__.py
index 3445a01d17..2740ff326b 100644
--- a/common/djangoapps/terrain/__init__.py
+++ b/common/djangoapps/terrain/__init__.py
@@ -4,3 +4,4 @@
from terrain.browser import *
from terrain.steps import *
from terrain.factories import *
+from terrain.start_stubs import *
diff --git a/common/djangoapps/terrain/start_stubs.py b/common/djangoapps/terrain/start_stubs.py
new file mode 100644
index 0000000000..aec88a1cc1
--- /dev/null
+++ b/common/djangoapps/terrain/start_stubs.py
@@ -0,0 +1,37 @@
+"""
+Initialize and teardown fake HTTP services for use in acceptance tests.
+"""
+
+from lettuce import before, after, world
+from django.conf import settings
+from terrain.stubs.youtube import StubYouTubeService
+from terrain.stubs.xqueue import StubXQueueService
+
+
+USAGE = "USAGE: python -m fakes.start SERVICE_NAME PORT_NUM"
+
+SERVICES = {
+ "youtube": {"port": settings.YOUTUBE_PORT, "class": StubYouTubeService},
+ "xqueue": {"port": settings.XQUEUE_PORT, "class": StubXQueueService},
+}
+
+
+@before.all
+def start_stubs():
+ """
+ Start each stub service running on a local port.
+ """
+ for name, service in SERVICES.iteritems():
+ fake_server = service['class'](port_num=service['port'])
+ setattr(world, name, fake_server)
+
+
+@after.all
+def stop_stubs(_):
+ """
+ Shut down each stub service.
+ """
+ for name in SERVICES.keys():
+ stub_server = getattr(world, name, None)
+ if stub_server is not None:
+ stub_server.shutdown()
diff --git a/common/lib/xmodule/xmodule/util/mock_youtube_server/__init__.py b/common/djangoapps/terrain/stubs/__init__.py
similarity index 100%
rename from common/lib/xmodule/xmodule/util/mock_youtube_server/__init__.py
rename to common/djangoapps/terrain/stubs/__init__.py
diff --git a/common/djangoapps/terrain/stubs/http.py b/common/djangoapps/terrain/stubs/http.py
new file mode 100644
index 0000000000..14dcf8ac2f
--- /dev/null
+++ b/common/djangoapps/terrain/stubs/http.py
@@ -0,0 +1,181 @@
+"""
+Stub implementation of an HTTP service.
+"""
+
+from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
+import urlparse
+import threading
+import json
+from lazy import lazy
+
+from logging import getLogger
+LOGGER = getLogger(__name__)
+
+
+class StubHttpRequestHandler(BaseHTTPRequestHandler, object):
+ """
+ Handler for the stub HTTP service.
+ """
+
+ protocol = "HTTP/1.0"
+
+ def log_message(self, format_str, *args):
+ """
+ Redirect messages to keep the test console clean.
+ """
+
+ msg = "{0} - - [{1}] {2}\n".format(
+ self.client_address[0],
+ self.log_date_time_string(),
+ format_str % args
+ )
+
+ LOGGER.debug(msg)
+
+ @lazy
+ def request_content(self):
+ """
+ Retrieve the content of the request.
+ """
+ try:
+ length = int(self.headers.getheader('content-length'))
+
+ except (TypeError, ValueError):
+ return ""
+ else:
+ return self.rfile.read(length)
+
+ @lazy
+ def post_dict(self):
+ """
+ Retrieve the request POST parameters from the client as a dictionary.
+ If no POST parameters can be interpreted, return an empty dict.
+ """
+ contents = self.request_content
+
+ # The POST dict will contain a list of values for each key.
+ # None of our parameters are lists, however, so we map [val] --> val
+ # If the list contains multiple entries, we pick the first one
+ try:
+ post_dict = urlparse.parse_qs(contents, keep_blank_values=True)
+ return {
+ key: list_val[0]
+ for key, list_val in post_dict.items()
+ }
+
+ except:
+ return dict()
+
+ @lazy
+ def get_params(self):
+ """
+ Return the GET parameters (querystring in the URL).
+ """
+ return urlparse.parse_qs(self.path)
+
+ def do_PUT(self):
+ """
+ Allow callers to configure the stub server using the /set_config URL.
+ """
+ if self.path == "/set_config" or self.path == "/set_config/":
+
+ for key, value in self.post_dict.iteritems():
+ self.log_message("Set config '{0}' to '{1}'".format(key, value))
+
+ try:
+ value = json.loads(value)
+
+ except ValueError:
+ self.log_message(u"Could not parse JSON: {0}".format(value))
+ self.send_response(400)
+
+ else:
+ self.server.set_config(unicode(key, 'utf-8'), value)
+ self.send_response(200)
+
+ else:
+ self.send_response(404)
+
+ def send_response(self, status_code, content=None, headers=None):
+ """
+ Send a response back to the client with the HTTP `status_code` (int),
+ `content` (str) and `headers` (dict).
+ """
+ self.log_message(
+ "Sent HTTP response: {0} with content '{1}' and headers {2}".format(status_code, content, headers)
+ )
+
+ if headers is None:
+ headers = dict()
+
+ BaseHTTPRequestHandler.send_response(self, status_code)
+
+ for (key, value) in headers.items():
+ self.send_header(key, value)
+
+ if len(headers) > 0:
+ self.end_headers()
+
+ if content is not None:
+ self.wfile.write(content)
+
+
+class StubHttpService(HTTPServer, object):
+ """
+ Stub HTTP service implementation.
+ """
+
+ # Subclasses override this to provide the handler class to use.
+ # Should be a subclass of `StubHttpRequestHandler`
+ HANDLER_CLASS = StubHttpRequestHandler
+
+ def __init__(self, port_num=0):
+ """
+ Configure the server to listen on localhost.
+ Default is to choose an arbitrary open port.
+ """
+ address = ('127.0.0.1', port_num)
+ HTTPServer.__init__(self, address, self.HANDLER_CLASS)
+
+ # Create a dict to store configuration values set by the client
+ self._config = dict()
+
+ # Start the server in a separate thread
+ server_thread = threading.Thread(target=self.serve_forever)
+ server_thread.daemon = True
+ server_thread.start()
+
+ # Log the port we're using to help identify port conflict errors
+ LOGGER.debug('Starting service on port {0}'.format(self.port))
+
+ def shutdown(self):
+ """
+ Stop the server and free up the port
+ """
+ # First call superclass shutdown()
+ HTTPServer.shutdown(self)
+
+ # We also need to manually close the socket
+ self.socket.close()
+
+ @property
+ def port(self):
+ """
+ Return the port that the service is listening on.
+ """
+ _, port = self.server_address
+ return port
+
+ def config(self, key, default=None):
+ """
+ Return the configuration value for `key`. If this
+ value has not been set, return `default` instead.
+ """
+ return self._config.get(key, default)
+
+ def set_config(self, key, value):
+ """
+ Set the configuration `value` for `key`.
+ """
+ self._config[key] = value
+
diff --git a/lms/djangoapps/courseware/mock_xqueue_server/__init__.py b/common/djangoapps/terrain/stubs/tests/__init__.py
similarity index 100%
rename from lms/djangoapps/courseware/mock_xqueue_server/__init__.py
rename to common/djangoapps/terrain/stubs/tests/__init__.py
diff --git a/common/djangoapps/terrain/stubs/tests/test_http.py b/common/djangoapps/terrain/stubs/tests/test_http.py
new file mode 100644
index 0000000000..fb09bad173
--- /dev/null
+++ b/common/djangoapps/terrain/stubs/tests/test_http.py
@@ -0,0 +1,58 @@
+"""
+Unit tests for stub HTTP server base class.
+"""
+
+import unittest
+import requests
+import json
+from terrain.stubs.http import StubHttpService
+
+
+class StubHttpServiceTest(unittest.TestCase):
+
+ def setUp(self):
+ self.server = StubHttpService()
+ self.addCleanup(self.server.shutdown)
+
+ def test_configure(self):
+ """
+ All HTTP stub servers have an end-point that allows
+ clients to configure how the server responds.
+ """
+ params = {
+ 'test_str': 'This is only a test',
+ 'test_int': 12345,
+ 'test_float': 123.45,
+ 'test_unicode': u'\u2603 the snowman',
+ 'test_dict': { 'test_key': 'test_val' }
+ }
+
+ for key, val in params.iteritems():
+ post_params = {key: json.dumps(val)}
+ response = requests.put(
+ "http://127.0.0.1:{0}/set_config".format(self.server.port),
+ data=post_params
+ )
+
+ self.assertEqual(response.status_code, 200)
+
+ # Check that the expected values were set in the configuration
+ for key, val in params.iteritems():
+ self.assertEqual(self.server.config(key), val)
+
+ def test_default_config(self):
+ self.assertEqual(self.server.config('not_set', default=42), 42)
+
+ def test_bad_json(self):
+ response = requests.put(
+ "http://127.0.0.1:{0}/set_config".format(self.server.port),
+ data="{,}"
+ )
+ self.assertEqual(response.status_code, 400)
+
+ def test_unknown_path(self):
+ response = requests.put(
+ "http://127.0.0.1:{0}/invalid_url".format(self.server.port),
+ data="{}"
+ )
+ self.assertEqual(response.status_code, 404)
diff --git a/common/djangoapps/terrain/stubs/tests/test_xqueue_stub.py b/common/djangoapps/terrain/stubs/tests/test_xqueue_stub.py
new file mode 100644
index 0000000000..222792ebb3
--- /dev/null
+++ b/common/djangoapps/terrain/stubs/tests/test_xqueue_stub.py
@@ -0,0 +1,74 @@
+"""
+Unit tests for stub XQueue implementation.
+"""
+
+import mock
+import unittest
+import json
+import urllib
+import time
+from terrain.stubs.xqueue import StubXQueueService
+
+
+class StubXQueueServiceTest(unittest.TestCase):
+
+ def setUp(self):
+ self.server = StubXQueueService()
+ self.url = "http://127.0.0.1:{0}".format(self.server.port)
+ self.addCleanup(self.server.shutdown)
+
+ # For testing purposes, do not delay the grading response
+ self.server.set_config('response_delay', 0)
+
+ @mock.patch('requests.post')
+ def test_grade_request(self, post):
+
+ # Send a grade request
+ callback_url = 'http://127.0.0.1:8000/test_callback'
+
+ grade_header = json.dumps({
+ 'lms_callback_url': callback_url,
+ 'lms_key': 'test_queuekey',
+ 'queue_name': 'test_queue'
+ })
+
+ grade_body = json.dumps({
+ 'student_info': 'test',
+ 'grader_payload': 'test',
+ 'student_response': 'test'
+ })
+
+ grade_request = {
+ 'xqueue_header': grade_header,
+ 'xqueue_body': grade_body
+ }
+
+ response_handle = urllib.urlopen(
+ self.url + '/xqueue/submit',
+ urllib.urlencode(grade_request)
+ )
+
+ response_dict = json.loads(response_handle.read())
+
+ # Expect that the response is success
+ self.assertEqual(response_dict['return_code'], 0)
+
+ # Expect that the server tries to post back the grading info
+ xqueue_body = json.dumps(
+ {'correct': True, 'score': 1, 'msg': '
'}
+ )
+
+ expected_callback_dict = {
+ 'xqueue_header': grade_header,
+ 'xqueue_body': xqueue_body
+ }
+
+ # Wait for the server to POST back to the callback URL
+ # Time out if it takes too long
+ start_time = time.time()
+ while time.time() - start_time < 5:
+ if post.called:
+ break
+
+ # Check that the POST request was made with the correct params
+ post.assert_called_with(callback_url, data=expected_callback_dict)
diff --git a/common/djangoapps/terrain/stubs/tests/test_youtube_stub.py b/common/djangoapps/terrain/stubs/tests/test_youtube_stub.py
new file mode 100644
index 0000000000..a3f048482c
--- /dev/null
+++ b/common/djangoapps/terrain/stubs/tests/test_youtube_stub.py
@@ -0,0 +1,58 @@
+"""
+Unit test for stub YouTube implementation.
+"""
+
+import unittest
+import requests
+from terrain.stubs.youtube import StubYouTubeService
+
+
+class StubYouTubeServiceTest(unittest.TestCase):
+
+ def setUp(self):
+ self.server = StubYouTubeService()
+ self.url = "http://127.0.0.1:{0}/".format(self.server.port)
+ self.server.set_config('time_to_response', 0.0)
+ self.addCleanup(self.server.shutdown)
+
+ def test_unused_url(self):
+ response = requests.get(self.url + 'unused_url')
+ self.assertEqual("Unused url", response.content)
+
+ def test_video_url(self):
+ response = requests.get(
+ self.url + 'test_youtube/OEoXaMPEzfM?v=2&alt=jsonc&callback=callback_func'
+ )
+
+ self.assertEqual('callback_func({"message": "I\'m youtube."})', response.content)
+
+ def test_transcript_url_equal(self):
+ response = requests.get(
+ self.url + 'test_transcripts_youtube/t__eq_exist'
+ )
+
+ self.assertEqual(
+ "".join([
+ '',
+ '',
+ 'Equal transcripts'
+ ]), response.content
+ )
+
+ def test_transcript_url_not_equal(self):
+ response = requests.get(
+ self.url + 'test_transcripts_youtube/t_neq_exist',
+ )
+
+ self.assertEqual(
+ "".join([
+ '',
+ '',
+ 'Transcripts sample, different that on server',
+ ''
+ ]), response.content
+ )
+
+ def test_transcript_not_found(self):
+ response = requests.get(self.url + 'test_transcripts_youtube/some_id')
+ self.assertEqual(404, response.status_code)
diff --git a/common/djangoapps/terrain/stubs/xqueue.py b/common/djangoapps/terrain/stubs/xqueue.py
new file mode 100644
index 0000000000..96e8e78f7b
--- /dev/null
+++ b/common/djangoapps/terrain/stubs/xqueue.py
@@ -0,0 +1,122 @@
+"""
+Stub implementation of XQueue for acceptance tests.
+"""
+
+from .http import StubHttpRequestHandler, StubHttpService
+import json
+import requests
+import threading
+
+
+class StubXQueueHandler(StubHttpRequestHandler):
+ """
+ A handler for XQueue POST requests.
+ """
+
+ DEFAULT_RESPONSE_DELAY = 2
+ DEFAULT_GRADE_RESPONSE = {'correct': True, 'score': 1, 'msg': ''}
+
+ def do_POST(self):
+ """
+ Handle a POST request from the client
+
+ Sends back an immediate success/failure response.
+ It then POSTS back to the client with grading results.
+ """
+ msg = "XQueue received POST request {0} to path {1}".format(self.post_dict, self.path)
+ self.log_message(msg)
+
+ # Respond only to grading requests
+ if self._is_grade_request():
+ try:
+ xqueue_header = json.loads(self.post_dict['xqueue_header'])
+ callback_url = xqueue_header['lms_callback_url']
+
+ except KeyError:
+ # If the message doesn't have a header or body,
+ # then it's malformed.
+ # Respond with failure
+ error_msg = "XQueue received invalid grade request"
+ self._send_immediate_response(False, message=error_msg)
+
+ except ValueError:
+ # If we could not decode the body or header,
+ # respond with failure
+
+ error_msg = "XQueue could not decode grade request"
+ self._send_immediate_response(False, message=error_msg)
+
+ else:
+ # Send an immediate response of success
+ # The grade request is formed correctly
+ self._send_immediate_response(True)
+
+ # Wait a bit before POSTing back to the callback url with the
+ # grade result configured by the server
+ # Otherwise, the problem will not realize it's
+ # queued and it will keep waiting for a response indefinitely
+ delayed_grade_func = lambda: self._send_grade_response(
+ callback_url, xqueue_header
+ )
+
+ threading.Timer(
+ self.server.config('response_delay', default=self.DEFAULT_RESPONSE_DELAY),
+ delayed_grade_func
+ ).start()
+
+ # If we get a request that's not to the grading submission
+ # URL, return an error
+ else:
+ error_message = "Invalid request URL"
+ self._send_immediate_response(False, message=error_message)
+
+ def _send_immediate_response(self, success, message=""):
+ """
+ Send an immediate success/failure message
+ back to the client
+ """
+
+ # Send the response indicating success/failure
+ response_str = json.dumps(
+ {'return_code': 0 if success else 1, 'content': message}
+ )
+
+ if self._is_grade_request():
+ self.send_response(
+ 200, content=response_str, headers={'Content-type': 'text/plain'}
+ )
+ self.log_message("XQueue: sent response {0}".format(response_str))
+
+ else:
+ self.send_response(500)
+
+ def _send_grade_response(self, postback_url, xqueue_header):
+ """
+ POST the grade response back to the client
+ using the response provided by the server configuration
+ """
+ # Get the grade response from the server configuration
+ grade_response = self.server.config('grade_response', default=self.DEFAULT_GRADE_RESPONSE)
+
+ # Wrap the message in tags to ensure that it is valid XML
+ if isinstance(grade_response, dict) and 'msg' in grade_response:
+ grade_response['msg'] = "
{0}
".format(grade_response['msg'])
+
+ data = {
+ 'xqueue_header': json.dumps(xqueue_header),
+ 'xqueue_body': json.dumps(grade_response)
+ }
+
+ requests.post(postback_url, data=data)
+ self.log_message("XQueue: sent grading response {0}".format(data))
+
+ def _is_grade_request(self):
+ return 'xqueue/submit' in self.path
+
+
+class StubXQueueService(StubHttpService):
+ """
+ A stub XQueue grading server that responds to POST requests to localhost.
+ """
+
+ HANDLER_CLASS = StubXQueueHandler
diff --git a/common/djangoapps/terrain/stubs/youtube.py b/common/djangoapps/terrain/stubs/youtube.py
new file mode 100644
index 0000000000..07e4b3c92a
--- /dev/null
+++ b/common/djangoapps/terrain/stubs/youtube.py
@@ -0,0 +1,85 @@
+"""
+Stub implementation of YouTube for acceptance tests.
+"""
+
+from .http import StubHttpRequestHandler, StubHttpService
+import json
+import time
+import requests
+
+
+class StubYouTubeHandler(StubHttpRequestHandler):
+ """
+ A handler for Youtube GET requests.
+ """
+
+ # Default number of seconds to delay the response to simulate network latency.
+ DEFAULT_DELAY_SEC = 0.5
+
+ def do_GET(self):
+ """
+ Handle a GET request from the client and sends response back.
+ """
+
+ self.log_message(
+ "Youtube provider received GET request to path {}".format(self.path)
+ )
+
+ if 'test_transcripts_youtube' in self.path:
+
+ if 't__eq_exist' in self.path:
+ status_message = "".join([
+ '',
+ '
',
+ 'Equal transcripts'
+ ])
+
+ self.send_response(
+ 200, content=status_message, headers={'Content-type': 'application/xml'}
+ )
+
+ elif 't_neq_exist' in self.path:
+ status_message = "".join([
+ '',
+ '
',
+ 'Transcripts sample, different that on server',
+ ''
+ ])
+
+ self.send_response(
+ 200, content=status_message, headers={'Content-type': 'application/xml'}
+ )
+
+ else:
+ self.send_response(404)
+
+ elif 'test_youtube' in self.path:
+ self._send_video_response("I'm youtube.")
+
+ else:
+ self.send_response(
+ 404, content="Unused url", headers={'Content-type': 'text/plain'}
+ )
+
+ def _send_video_response(self, message):
+ """
+ Send message back to the client for video player requests.
+ Requires sending back callback id.
+ """
+ # Delay the response to simulate network latency
+ time.sleep(self.server.config('time_to_response', self.DEFAULT_DELAY_SEC))
+
+ # Construct the response content
+ callback = self.get_params['callback'][0]
+ response = callback + '({})'.format(json.dumps({'message': message}))
+
+ self.send_response(200, content=response, headers={'Content-type': 'text/html'})
+ self.log_message("Youtube: sent response {}".format(message))
+
+
+class StubYouTubeService(StubHttpService):
+ """
+ A stub Youtube provider server that responds to GET requests to localhost.
+ """
+
+ HANDLER_CLASS = StubYouTubeHandler
diff --git a/common/djangoapps/terrain/ui_helpers.py b/common/djangoapps/terrain/ui_helpers.py
index 2e6a6efc3b..bc61c0eed6 100644
--- a/common/djangoapps/terrain/ui_helpers.py
+++ b/common/djangoapps/terrain/ui_helpers.py
@@ -10,7 +10,7 @@ from textwrap import dedent
from urllib import quote_plus
from selenium.common.exceptions import (
WebDriverException, TimeoutException,
- StaleElementReferenceException)
+ StaleElementReferenceException, InvalidElementStateException)
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
@@ -581,7 +581,7 @@ def trigger_event(css_selector, event='change', index=0):
@world.absorb
-def retry_on_exception(func, max_attempts=5, ignored_exceptions=StaleElementReferenceException):
+def retry_on_exception(func, max_attempts=5, ignored_exceptions=(StaleElementReferenceException, InvalidElementStateException)):
"""
Retry the interaction, ignoring the passed exceptions.
By default ignore StaleElementReferenceException, which happens often in our application
diff --git a/common/lib/xmodule/xmodule/util/mock_youtube_server/mock_youtube_server.py b/common/lib/xmodule/xmodule/util/mock_youtube_server/mock_youtube_server.py
deleted file mode 100644
index 3fc7c8285e..0000000000
--- a/common/lib/xmodule/xmodule/util/mock_youtube_server/mock_youtube_server.py
+++ /dev/null
@@ -1,125 +0,0 @@
-from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
-import json
-import mock
-import sys
-import threading
-import time
-import urlparse
-from logging import getLogger
-
-
-logger = getLogger(__name__)
-
-class MockYoutubeRequestHandler(BaseHTTPRequestHandler):
- '''
- A handler for Youtube GET requests.
- '''
-
- protocol = "HTTP/1.0"
-
- def log_message(self, format, *args):
- """Log an arbitrary message."""
- # Code copied from BaseHTTPServer.py. Changed to write to sys.stdout
- # so that messages won't pollute test output.
- sys.stdout.write("%s - - [%s] %s\n" %
- (self.client_address[0],
- self.log_date_time_string(),
- format % args))
-
- def do_HEAD(self):
- code = 200
- if 'test_transcripts_youtube' in self.path:
- if not 'trans_exist' in self.path:
- code = 404
- self._send_head(code)
-
- def do_GET(self):
- '''
- Handle a GET request from the client and sends response back.
- '''
- logger.debug("Youtube provider received GET request to path {}".format(
- self.path)
- ) # Log the request
-
- if 'test_transcripts_youtube' in self.path:
- if 't__eq_exist' in self.path:
- status_message = """
Equal transcripts"""
- self._send_head()
- self._send_transcripts_response(status_message)
- elif 't_neq_exist' in self.path:
- status_message = """
Transcripts sample, different that on server"""
- self._send_head()
- self._send_transcripts_response(status_message)
- else:
- self._send_head(404)
- elif 'test_youtube' in self.path:
- self._send_head()
- #testing videoplayers
- status_message = "I'm youtube."
- response_timeout = float(self.server.time_to_response)
-
- # threading timer produces TypeError: 'NoneType' object is not callable here
- # so we use time.sleep, as we already in separate thread.
- time.sleep(response_timeout)
- self._send_video_response(status_message)
- else:
- # unused url
- self._send_head()
- self._send_transcripts_response('Unused url')
- logger.debug("Request to unused url.")
-
- def _send_head(self, code=200):
- '''
- Send the response code and MIME headers
- '''
-
- self.send_response(code)
- self.send_header('Content-type', 'text/html')
- self.end_headers()
-
- def _send_transcripts_response(self, message):
- '''
- Send message back to the client for transcripts ajax requests.
- '''
- response = message
- # Log the response
- logger.debug("Youtube: sent response {}".format(message))
-
- self.wfile.write(response)
-
- def _send_video_response(self, message):
- '''
- Send message back to the client for video player requests.
- Requires sending back callback id.
- '''
- callback = urlparse.parse_qs(self.path)['callback'][0]
- response = callback + '({})'.format(json.dumps({'message': message}))
- # Log the response
- logger.debug("Youtube: sent response {}".format(message))
-
- self.wfile.write(response)
-
-
-class MockYoutubeServer(HTTPServer):
- '''
- A mock Youtube provider server that responds
- to GET requests to localhost.
- '''
-
- def __init__(self, address):
- '''
- Initialize the mock XQueue server instance.
-
- *address* is the (host, host's port to listen to) tuple.
- '''
- handler = MockYoutubeRequestHandler
- HTTPServer.__init__(self, address, handler)
-
- def shutdown(self):
- '''
- Stop the server and free up the port
- '''
- # First call superclass shutdown()
- HTTPServer.shutdown(self)
- # We also need to manually close the socket
- self.socket.close()
diff --git a/common/lib/xmodule/xmodule/util/mock_youtube_server/test_mock_youtube_server.py b/common/lib/xmodule/xmodule/util/mock_youtube_server/test_mock_youtube_server.py
deleted file mode 100644
index bb65760905..0000000000
--- a/common/lib/xmodule/xmodule/util/mock_youtube_server/test_mock_youtube_server.py
+++ /dev/null
@@ -1,77 +0,0 @@
-"""
-Test for Mock_Youtube_Server
-"""
-import unittest
-import threading
-import requests
-from mock_youtube_server import MockYoutubeServer
-
-
-class MockYoutubeServerTest(unittest.TestCase):
- '''
- A mock version of the YouTube provider server that listens on a local
- port and responds with jsonp.
-
- Used for lettuce BDD tests in lms/courseware/features/video.feature
- '''
-
- def setUp(self):
-
- # Create the server
- server_port = 8034
- server_host = '127.0.0.1'
- address = (server_host, server_port)
- self.server = MockYoutubeServer(address, )
- self.server.time_to_response = 0.5
- # Start the server in a separate daemon thread
- server_thread = threading.Thread(target=self.server.serve_forever)
- server_thread.daemon = True
- server_thread.start()
-
- def tearDown(self):
-
- # Stop the server, freeing up the port
- self.server.shutdown()
-
- def test_request(self):
- """
- Tests that Youtube server processes request with right program
- path, and responses with incorrect signature.
- """
- # GET request
-
- # unused url
- response = requests.get(
- 'http://127.0.0.1:8034/some url',
- )
- self.assertEqual("Unused url", response.content)
-
- # video player test url, callback shoud be presented in url params
- response = requests.get(
- 'http://127.0.0.1:8034/test_youtube/OEoXaMPEzfM?v=2&alt=jsonc&callback=callback_func',
- )
- self.assertEqual("""callback_func({"message": "I\'m youtube."})""", response.content)
-
- # transcripts test url
- response = requests.get(
- 'http://127.0.0.1:8034/test_transcripts_youtube/t__eq_exist',
- )
- self.assertEqual(
- '
Equal transcripts',
- response.content
- )
-
- # transcripts test url
- response = requests.get(
- 'http://127.0.0.1:8034/test_transcripts_youtube/t_neq_exist',
- )
- self.assertEqual(
- '
Transcripts sample, different that on server',
- response.content
- )
-
- # transcripts test url, not trans_exist youtube_id, so 404 should be returned
- response = requests.get(
- 'http://127.0.0.1:8034/test_transcripts_youtube/some_id',
- )
- self.assertEqual(404, response.status_code)
diff --git a/common/lib/xmodule/xmodule/video_module.py b/common/lib/xmodule/xmodule/video_module.py
index b7ca37b16e..576c32782e 100644
--- a/common/lib/xmodule/xmodule/video_module.py
+++ b/common/lib/xmodule/xmodule/video_module.py
@@ -167,12 +167,6 @@ class VideoModule(VideoFields, XModule):
sources = {get_ext(src): src for src in self.html5_sources}
sources['main'] = self.source
- # for testing Youtube timeout in acceptance tests
- if getattr(settings, 'VIDEO_PORT', None):
- yt_test_url = "http://127.0.0.1:" + str(settings.VIDEO_PORT) + '/test_youtube/'
- else:
- yt_test_url = 'https://gdata.youtube.com/feeds/api/videos/'
-
return self.system.render_template('video.html', {
'youtube_streams': _create_youtube_string(self),
'id': self.location.html_id(),
@@ -191,7 +185,7 @@ class VideoModule(VideoFields, XModule):
# TODO: Later on the value 1500 should be taken from some global
# configuration setting field.
'yt_test_timeout': 1500,
- 'yt_test_url': yt_test_url
+ 'yt_test_url': settings.YOUTUBE_TEST_URL
})
diff --git a/lms/djangoapps/courseware/features/lti_setup.py b/lms/djangoapps/courseware/features/lti_setup.py
index e86aa78ed2..40fb2ee969 100644
--- a/lms/djangoapps/courseware/features/lti_setup.py
+++ b/lms/djangoapps/courseware/features/lti_setup.py
@@ -14,9 +14,7 @@ logger = getLogger(__name__)
def setup_mock_lti_server():
server_host = '127.0.0.1'
-
- # Add +1 to XQUEUE random port number
- server_port = settings.XQUEUE_PORT + 1
+ server_port = settings.LTI_PORT
address = (server_host, server_port)
diff --git a/lms/djangoapps/courseware/features/problems.py b/lms/djangoapps/courseware/features/problems.py
index 0be5b72284..278855a761 100644
--- a/lms/djangoapps/courseware/features/problems.py
+++ b/lms/djangoapps/courseware/features/problems.py
@@ -73,7 +73,7 @@ def set_external_grader_response(step, correctness):
# Set the fake xqueue server to always respond
# correct/incorrect when asked to grade a problem
- world.xqueue_server.set_grade_response(response_dict)
+ world.xqueue.set_config('grade_response', response_dict)
@step(u'I answer a "([^"]*)" problem "([^"]*)ly"')
diff --git a/lms/djangoapps/courseware/features/video.feature b/lms/djangoapps/courseware/features/video.feature
index 7518bf6bdb..1ca82b02a1 100644
--- a/lms/djangoapps/courseware/features/video.feature
+++ b/lms/djangoapps/courseware/features/video.feature
@@ -18,7 +18,7 @@ Feature: LMS.Video component
# 3
# Youtube testing
Scenario: Video component is fully rendered in the LMS in Youtube mode with HTML5 sources
- Given youtube server is up and response time is 0.4 seconds
+ Given youtube server is up and response time is 0.4 seconds
And the course has a Video component in Youtube_HTML5 mode
Then when I view the video it has rendered in Youtube mode
diff --git a/lms/djangoapps/courseware/features/video.py b/lms/djangoapps/courseware/features/video.py
index 57fc8a0a9a..3635373437 100644
--- a/lms/djangoapps/courseware/features/video.py
+++ b/lms/djangoapps/courseware/features/video.py
@@ -83,7 +83,7 @@ def add_video_to_course(course, player_mode):
@step('youtube server is up and response time is (.*) seconds$')
def set_youtube_response_timeout(_step, time):
- world.youtube_server.time_to_response = time
+ world.youtube.set_config('time_to_response', float(time))
@step('when I view the video it has rendered in (.*) mode$')
diff --git a/lms/djangoapps/courseware/features/xqueue_setup.py b/lms/djangoapps/courseware/features/xqueue_setup.py
deleted file mode 100644
index 90a68961ee..0000000000
--- a/lms/djangoapps/courseware/features/xqueue_setup.py
+++ /dev/null
@@ -1,35 +0,0 @@
-#pylint: disable=C0111
-#pylint: disable=W0621
-
-from courseware.mock_xqueue_server.mock_xqueue_server import MockXQueueServer
-from lettuce import before, after, world
-from django.conf import settings
-import threading
-
-@before.all
-def setup_mock_xqueue_server():
-
- # Retrieve the local port from settings
- server_port = settings.XQUEUE_PORT
-
- # Create the mock server instance
- server = MockXQueueServer(server_port)
-
- # Start the server running in a separate daemon thread
- # Because the thread is a daemon, it will terminate
- # when the main thread terminates.
- server_thread = threading.Thread(target=server.serve_forever)
- server_thread.daemon = True
- server_thread.start()
-
- # Store the server instance in lettuce's world
- # so that other steps can access it
- # (and we can shut it down later)
- world.xqueue_server = server
-
-
-@after.all
-def teardown_mock_xqueue_server(total):
-
- # Stop the xqueue server and free up the port
- world.xqueue_server.shutdown()
diff --git a/lms/djangoapps/courseware/features/youtube_setup.py b/lms/djangoapps/courseware/features/youtube_setup.py
deleted file mode 100644
index b9e536c5fe..0000000000
--- a/lms/djangoapps/courseware/features/youtube_setup.py
+++ /dev/null
@@ -1,46 +0,0 @@
-#pylint: disable=C0111
-#pylint: disable=W0621
-from xmodule.util.mock_youtube_server.mock_youtube_server import MockYoutubeServer
-from lettuce import before, after, world
-from django.conf import settings
-import threading
-
-from logging import getLogger
-logger = getLogger(__name__)
-
-
-@before.all
-def setup_mock_youtube_server():
- # import ipdb; ipdb.set_trace()
- server_host = '127.0.0.1'
-
- server_port = settings.VIDEO_PORT
-
- address = (server_host, server_port)
-
- # Create the mock server instance
- server = MockYoutubeServer(address)
- logger.debug("Youtube server started at {} port".format(str(server_port)))
-
- server.time_to_response = 1 # seconds
-
- server.address = address
-
- # Start the server running in a separate daemon thread
- # Because the thread is a daemon, it will terminate
- # when the main thread terminates.
- server_thread = threading.Thread(target=server.serve_forever)
- server_thread.daemon = True
- server_thread.start()
-
- # Store the server instance in lettuce's world
- # so that other steps can access it
- # (and we can shut it down later)
- world.youtube_server = server
-
-
-@after.all
-def teardown_mock_youtube_server(total):
-
- # Stop the LTI server and free up the port
- world.youtube_server.shutdown()
diff --git a/lms/djangoapps/courseware/mock_xqueue_server/mock_xqueue_server.py b/lms/djangoapps/courseware/mock_xqueue_server/mock_xqueue_server.py
deleted file mode 100644
index 7bc1b95662..0000000000
--- a/lms/djangoapps/courseware/mock_xqueue_server/mock_xqueue_server.py
+++ /dev/null
@@ -1,211 +0,0 @@
-from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
-import json
-import urllib
-import urlparse
-import threading
-
-from logging import getLogger
-logger = getLogger(__name__)
-
-
-class MockXQueueRequestHandler(BaseHTTPRequestHandler):
- '''
- A handler for XQueue POST requests.
- '''
-
- protocol = "HTTP/1.0"
-
- def do_HEAD(self):
- self._send_head()
-
- def do_POST(self):
- '''
- Handle a POST request from the client
-
- Sends back an immediate success/failure response.
- It then POSTS back to the client
- with grading results, as configured in MockXQueueServer.
- '''
- self._send_head()
-
- # Retrieve the POST data
- post_dict = self._post_dict()
-
- # Log the request
- logger.debug("XQueue received POST request %s to path %s" %
- (str(post_dict), self.path))
-
- # Respond only to grading requests
- if self._is_grade_request():
- try:
- xqueue_header = json.loads(post_dict['xqueue_header'])
- xqueue_body = json.loads(post_dict['xqueue_body'])
-
- callback_url = xqueue_header['lms_callback_url']
-
- except KeyError:
- # If the message doesn't have a header or body,
- # then it's malformed.
- # Respond with failure
- error_msg = "XQueue received invalid grade request"
- self._send_immediate_response(False, message=error_msg)
-
- except ValueError:
- # If we could not decode the body or header,
- # respond with failure
-
- error_msg = "XQueue could not decode grade request"
- self._send_immediate_response(False, message=error_msg)
-
- else:
- # Send an immediate response of success
- # The grade request is formed correctly
- self._send_immediate_response(True)
-
- # Wait a bit before POSTing back to the callback url with the
- # grade result configured by the server
- # Otherwise, the problem will not realize it's
- # queued and it will keep waiting for a response
- # indefinitely
- delayed_grade_func = lambda: self._send_grade_response(callback_url,
- xqueue_header)
-
- timer = threading.Timer(2, delayed_grade_func)
- timer.start()
-
- # If we get a request that's not to the grading submission
- # URL, return an error
- else:
- error_message = "Invalid request URL"
- self._send_immediate_response(False, message=error_message)
-
-
- def _send_head(self):
- '''
- Send the response code and MIME headers
- '''
- if self._is_grade_request():
- self.send_response(200)
- else:
- self.send_response(500)
-
- self.send_header('Content-type', 'text/plain')
- self.end_headers()
-
- def _post_dict(self):
- '''
- Retrieve the POST parameters from the client as a dictionary
- '''
-
- try:
- length = int(self.headers.getheader('content-length'))
-
- post_dict = urlparse.parse_qs(self.rfile.read(length))
-
- # The POST dict will contain a list of values
- # for each key.
- # None of our parameters are lists, however,
- # so we map [val] --> val
- # If the list contains multiple entries,
- # we pick the first one
- post_dict = dict(map(lambda (key, list_val): (key, list_val[0]),
- post_dict.items()))
-
- except:
- # We return an empty dict here, on the assumption
- # that when we later check that the request has
- # the correct fields, it won't find them,
- # and will therefore send an error response
- return {}
-
- return post_dict
-
- def _send_immediate_response(self, success, message=""):
- '''
- Send an immediate success/failure message
- back to the client
- '''
-
- # Send the response indicating success/failure
- response_str = json.dumps({'return_code': 0 if success else 1,
- 'content': message})
-
- # Log the response
- logger.debug("XQueue: sent response %s" % response_str)
-
- self.wfile.write(response_str)
-
- def _send_grade_response(self, postback_url, xqueue_header):
- '''
- POST the grade response back to the client
- using the response provided by the server configuration
- '''
- response_dict = {'xqueue_header': json.dumps(xqueue_header),
- 'xqueue_body': json.dumps(self.server.grade_response())}
-
- # Log the response
- logger.debug("XQueue: sent grading response %s" % str(response_dict))
-
- MockXQueueRequestHandler.post_to_url(postback_url, response_dict)
-
- def _is_grade_request(self):
- return 'xqueue/submit' in self.path
-
- @staticmethod
- def post_to_url(url, param_dict):
- '''
- POST *param_dict* to *url*
- We make this a separate function so we can easily patch
- it during testing.
- '''
- urllib.urlopen(url, urllib.urlencode(param_dict))
-
-
-class MockXQueueServer(HTTPServer):
- '''
- A mock XQueue grading server that responds
- to POST requests to localhost.
- '''
-
- def __init__(self, port_num,
- grade_response_dict={'correct': True, 'score': 1, 'msg': ''}):
- '''
- Initialize the mock XQueue server instance.
-
- *port_num* is the localhost port to listen to
-
- *grade_response_dict* is a dictionary that will be JSON-serialized
- and sent in response to XQueue grading requests.
- '''
-
- self.set_grade_response(grade_response_dict)
-
- handler = MockXQueueRequestHandler
- address = ('', port_num)
- HTTPServer.__init__(self, address, handler)
-
- def shutdown(self):
- '''
- Stop the server and free up the port
- '''
- # First call superclass shutdown()
- HTTPServer.shutdown(self)
-
- # We also need to manually close the socket
- self.socket.close()
-
- def grade_response(self):
- return self._grade_response
-
- def set_grade_response(self, grade_response_dict):
-
- # Check that the grade response has the right keys
- assert('correct' in grade_response_dict and
- 'score' in grade_response_dict and
- 'msg' in grade_response_dict)
-
- # Wrap the message in
tags to ensure that it is valid XML
- grade_response_dict['msg'] = "
%s
" % grade_response_dict['msg']
-
- # Save the response dictionary
- self._grade_response = grade_response_dict
diff --git a/lms/djangoapps/courseware/mock_xqueue_server/test_mock_xqueue_server.py b/lms/djangoapps/courseware/mock_xqueue_server/test_mock_xqueue_server.py
deleted file mode 100644
index 3f9a8e5b42..0000000000
--- a/lms/djangoapps/courseware/mock_xqueue_server/test_mock_xqueue_server.py
+++ /dev/null
@@ -1,84 +0,0 @@
-import mock
-import unittest
-import threading
-import json
-import urllib
-import time
-from mock_xqueue_server import MockXQueueServer, MockXQueueRequestHandler
-
-from nose.plugins.skip import SkipTest
-
-
-class MockXQueueServerTest(unittest.TestCase):
- '''
- A mock version of the XQueue server that listens on a local
- port and responds with pre-defined grade messages.
-
- Used for lettuce BDD tests in lms/courseware/features/problems.feature
- and lms/courseware/features/problems.py
-
- This is temporary and will be removed when XQueue is
- rewritten using celery.
- '''
-
- def setUp(self):
-
- # This is a test of the test setup,
- # so it does not need to run as part of the unit test suite
- # You can re-enable it by commenting out the line below
- raise SkipTest
-
- # Create the server
- server_port = 8034
- self.server_url = 'http://127.0.0.1:%d' % server_port
- self.server = MockXQueueServer(server_port,
- {'correct': True, 'score': 1, 'msg': ''})
-
- # Start the server in a separate daemon thread
- server_thread = threading.Thread(target=self.server.serve_forever)
- server_thread.daemon = True
- server_thread.start()
-
- def tearDown(self):
-
- # Stop the server, freeing up the port
- self.server.shutdown()
-
- def test_grade_request(self):
-
- # Patch post_to_url() so we can intercept
- # outgoing POST requests from the server
- MockXQueueRequestHandler.post_to_url = mock.Mock()
-
- # Send a grade request
- callback_url = 'http://127.0.0.1:8000/test_callback'
-
- grade_header = json.dumps({'lms_callback_url': callback_url,
- 'lms_key': 'test_queuekey',
- 'queue_name': 'test_queue'})
-
- grade_body = json.dumps({'student_info': 'test',
- 'grader_payload': 'test',
- 'student_response': 'test'})
-
- grade_request = {'xqueue_header': grade_header,
- 'xqueue_body': grade_body}
-
- response_handle = urllib.urlopen(self.server_url + '/xqueue/submit',
- urllib.urlencode(grade_request))
-
- response_dict = json.loads(response_handle.read())
-
- # Expect that the response is success
- self.assertEqual(response_dict['return_code'], 0)
-
- # Wait a bit before checking that the server posted back
- time.sleep(3)
-
- # Expect that the server tries to post back the grading info
- xqueue_body = json.dumps({'correct': True, 'score': 1,
- 'msg': '
'})
- expected_callback_dict = {'xqueue_header': grade_header,
- 'xqueue_body': xqueue_body}
- MockXQueueRequestHandler.post_to_url.assert_called_with(callback_url,
- expected_callback_dict)
diff --git a/lms/envs/acceptance.py b/lms/envs/acceptance.py
index 6b4b5f7665..a10ff9a164 100644
--- a/lms/envs/acceptance.py
+++ b/lms/envs/acceptance.py
@@ -152,6 +152,7 @@ SELENIUM_GRID = {
'BROWSER': LETTUCE_BROWSER,
}
+
#####################################################################
# See if the developer has any local overrides.
try:
@@ -161,22 +162,9 @@ except ImportError:
# Because an override for where to run will affect which ports to use,
# set these up after the local overrides.
-if LETTUCE_SELENIUM_CLIENT == 'saucelabs':
- LETTUCE_SERVER_PORT = choice(PORTS)
- PORTS.remove(LETTUCE_SERVER_PORT)
-else:
- LETTUCE_SERVER_PORT = randint(1024, 65535)
-
-# Set up XQueue information so that the lms will send
-# requests to a mock XQueue server running locally
-if LETTUCE_SELENIUM_CLIENT == 'saucelabs':
- XQUEUE_PORT = choice(PORTS)
- PORTS.remove(XQUEUE_PORT)
-else:
- XQUEUE_PORT = randint(1024, 65535)
-
+# Configure XQueue interface to use our stub XQueue server
XQUEUE_INTERFACE = {
- "url": "http://127.0.0.1:%d" % XQUEUE_PORT,
+ "url": "http://127.0.0.1:{0:d}".format(XQUEUE_PORT),
"django_auth": {
"username": "lms",
"password": "***REMOVED***"
@@ -184,10 +172,5 @@ XQUEUE_INTERFACE = {
"basic_auth": ('anant', 'agarwal'),
}
-# Set up Video information so that the lms will send
-# requests to a mock Youtube server running locally
-if LETTUCE_SELENIUM_CLIENT == 'saucelabs':
- VIDEO_PORT = choice(PORTS)
- PORTS.remove(VIDEO_PORT)
-else:
- VIDEO_PORT = randint(1024, 65535)
+# Point the URL used to test YouTube availability to our stub YouTube server
+YOUTUBE_TEST_URL = "http://127.0.0.1:{0}/test_youtube/".format(YOUTUBE_PORT)
diff --git a/lms/envs/common.py b/lms/envs/common.py
index 893e537f77..0ca13d9f8c 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -911,6 +911,11 @@ BULK_EMAIL_LOG_SENT_EMAILS = False
BULK_EMAIL_RETRY_DELAY_BETWEEN_SENDS = 0.02
+############################## Video ##########################################
+
+# URL to test YouTube availability
+YOUTUBE_TEST_URL = 'https://gdata.youtube.com/feeds/api/videos/'
+
################################### APPS ######################################
INSTALLED_APPS = (
diff --git a/lms/envs/test.py b/lms/envs/test.py
index 31636f6aad..eb08cef9bf 100644
--- a/lms/envs/test.py
+++ b/lms/envs/test.py
@@ -239,6 +239,16 @@ FILE_UPLOAD_HANDLERS = (
'django.core.files.uploadhandler.TemporaryFileUploadHandler',
)
+########################### Server Ports ###################################
+
+# These ports are carefully chosen so that if the browser needs to
+# access them, they will be available through the SauceLabs SSH tunnel
+LETTUCE_SERVER_PORT = 8003
+XQUEUE_PORT = 8040
+YOUTUBE_PORT = 8031
+LTI_PORT = 8765
+
+
################### Make tests faster
#http://slacy.com/blog/2012/04/make-your-tests-faster-in-django-1-4/