Commit 7bc697a1 by Don Mitchell

Parse urls in one regex w/ more consistency

parent 1db76191
...@@ -13,7 +13,7 @@ from bson.errors import InvalidId ...@@ -13,7 +13,7 @@ from bson.errors import InvalidId
from xmodule.modulestore.exceptions import InsufficientSpecificationError, OverSpecificationError from xmodule.modulestore.exceptions import InsufficientSpecificationError, OverSpecificationError
from .parsers import parse_url, parse_course_id, parse_block_ref from .parsers import parse_url, parse_course_id, parse_block_ref
from .parsers import BRANCH_PREFIX, BLOCK_PREFIX, URL_VERSION_PREFIX from .parsers import BRANCH_PREFIX, BLOCK_PREFIX, VERSION_PREFIX
import re import re
from xmodule.modulestore import Location from xmodule.modulestore import Location
...@@ -190,8 +190,8 @@ class CourseLocator(Locator): ...@@ -190,8 +190,8 @@ class CourseLocator(Locator):
self.init_from_version_guid(version_guid) self.init_from_version_guid(version_guid)
if course_id or branch: if course_id or branch:
self.init_from_course_id(course_id, branch) self.init_from_course_id(course_id, branch)
assert self.version_guid or self.course_id, \ if self.version_guid is None and self.course_id is None:
"Either version_guid or course_id should be set." raise ValueError("Either version_guid or course_id should be set: {}".format(url))
def __unicode__(self): def __unicode__(self):
""" """
...@@ -200,10 +200,10 @@ class CourseLocator(Locator): ...@@ -200,10 +200,10 @@ class CourseLocator(Locator):
if self.course_id: if self.course_id:
result = self.course_id result = self.course_id
if self.branch: if self.branch:
result += BRANCH_PREFIX + self.branch result += '/' + BRANCH_PREFIX + self.branch
return result return result
elif self.version_guid: elif self.version_guid:
return URL_VERSION_PREFIX + str(self.version_guid) return VERSION_PREFIX + str(self.version_guid)
else: else:
# raise InsufficientSpecificationError("missing course_id or version_guid") # raise InsufficientSpecificationError("missing course_id or version_guid")
return '<InsufficientSpecificationError: missing course_id or version_guid>' return '<InsufficientSpecificationError: missing course_id or version_guid>'
...@@ -263,22 +263,46 @@ class CourseLocator(Locator): ...@@ -263,22 +263,46 @@ class CourseLocator(Locator):
version_guid=self.version_guid, version_guid=self.version_guid,
branch=self.branch) branch=self.branch)
def url_reverse(self, prefix, postfix):
"""
Do what reverse is supposed to do but seems unable to do. Generate a url using prefix unicode(self) postfix
:param prefix: the beginning of the url (should begin and end with / if non-empty)
:param postfix: the part to append to the url (should begin w/ / if non-empty)
"""
if prefix:
if not prefix.endswith('/'):
prefix += '/'
if not prefix.startswith('/'):
prefix = '/' + prefix
else:
prefix = '/'
if postfix and not postfix.startswith('/'):
postfix = '/' + postfix
return prefix + unicode(self) + postfix
def reverse_kwargs(self):
"""
Get the kwargs list to supply to reverse (basically, a dict of the set properties)
"""
return {key: value for key, value in self.__dict__.iteritems() if value is not None}
def init_from_url(self, url): def init_from_url(self, url):
""" """
url must be a string beginning with 'edx://' and containing url must be a string beginning with 'edx://' and containing
either a valid version_guid or course_id (with optional branch), or both. either a valid version_guid or course_id (with optional branch), or both.
""" """
if isinstance(url, Locator): if isinstance(url, Locator):
url = url.url() parse = url.__dict__
if not isinstance(url, basestring): elif not isinstance(url, basestring):
raise TypeError('%s is not an instance of basestring' % url) raise TypeError('%s is not an instance of basestring' % url)
else:
parse = parse_url(url, tag_optional=True) parse = parse_url(url, tag_optional=True)
if not parse: if not parse:
raise ValueError('Could not parse "%s" as a url' % url) raise ValueError('Could not parse "%s" as a url' % url)
self._set_value( self._set_value(
parse, 'version_guid', lambda (new_guid): self.set_version_guid(self.as_object_id(new_guid)) parse, 'version_guid', lambda (new_guid): self.set_version_guid(self.as_object_id(new_guid))
) )
self._set_value(parse, 'id', lambda (new_id): self.set_course_id(new_id)) self._set_value(parse, 'course_id', lambda (new_id): self.set_course_id(new_id))
self._set_value(parse, 'branch', lambda (new_branch): self.set_branch(new_branch)) self._set_value(parse, 'branch', lambda (new_branch): self.set_branch(new_branch))
def init_from_version_guid(self, version_guid): def init_from_version_guid(self, version_guid):
...@@ -313,9 +337,9 @@ class CourseLocator(Locator): ...@@ -313,9 +337,9 @@ class CourseLocator(Locator):
raise ValueError("%s does not have a valid course_id" % course_id) raise ValueError("%s does not have a valid course_id" % course_id)
parse = parse_course_id(course_id) parse = parse_course_id(course_id)
if not parse: if not parse or parse['course_id'] is None:
raise ValueError('Could not parse "%s" as a course_id' % course_id) raise ValueError('Could not parse "%s" as a course_id' % course_id)
self.set_course_id(parse['id']) self.set_course_id(parse['course_id'])
rev = parse['branch'] rev = parse['branch']
if rev: if rev:
self.set_branch(rev) self.set_branch(rev)
...@@ -396,11 +420,12 @@ class BlockUsageLocator(CourseLocator): ...@@ -396,11 +420,12 @@ class BlockUsageLocator(CourseLocator):
self.init_block_ref_from_course_id(course_id) self.init_block_ref_from_course_id(course_id)
if usage_id: if usage_id:
self.init_block_ref(usage_id) self.init_block_ref(usage_id)
CourseLocator.__init__(self, super(BlockUsageLocator, self).__init__(
url=url, url=url,
version_guid=version_guid, version_guid=version_guid,
course_id=course_id, course_id=course_id,
branch=branch) branch=branch
)
def is_initialized(self): def is_initialized(self):
""" """
...@@ -427,6 +452,16 @@ class BlockUsageLocator(CourseLocator): ...@@ -427,6 +452,16 @@ class BlockUsageLocator(CourseLocator):
branch=self.branch, branch=self.branch,
usage_id=self.usage_id) usage_id=self.usage_id)
def reverse_kwargs(self):
"""
Get the kwargs list to supply to reverse (basically, a dict of the set properties)
"""
result = super(BlockUsageLocator, self).reverse_kwargs()
if self.usage_id:
del result['usage_id']
result['block'] = self.usage_id
return result
def set_usage_id(self, new): def set_usage_id(self, new):
""" """
Initialize usage_id to new value. Initialize usage_id to new value.
...@@ -459,10 +494,12 @@ class BlockUsageLocator(CourseLocator): ...@@ -459,10 +494,12 @@ class BlockUsageLocator(CourseLocator):
def init_block_ref_from_course_id(self, course_id): def init_block_ref_from_course_id(self, course_id):
if isinstance(course_id, CourseLocator): if isinstance(course_id, CourseLocator):
# FIXME the parsed course_id should never contain a block ref
course_id = course_id.course_id course_id = course_id.course_id
assert course_id, "%s does not have a valid course_id" assert course_id, "%s does not have a valid course_id"
parse = parse_course_id(course_id) parse = parse_course_id(course_id)
assert parse, 'Could not parse "%s" as a course_id' % course_id if parse is None:
raise ValueError('Could not parse "%s" as a course_id' % course_id)
self._set_value(parse, 'block', lambda(new_block): self.set_usage_id(new_block)) self._set_value(parse, 'block', lambda(new_block): self.set_usage_id(new_block))
def __unicode__(self): def __unicode__(self):
...@@ -470,7 +507,7 @@ class BlockUsageLocator(CourseLocator): ...@@ -470,7 +507,7 @@ class BlockUsageLocator(CourseLocator):
Return a string representing this location. Return a string representing this location.
""" """
rep = super(BlockUsageLocator, self).__unicode__() rep = super(BlockUsageLocator, self).__unicode__()
return rep + BLOCK_PREFIX + unicode(self.usage_id) return rep + '/' + BLOCK_PREFIX + unicode(self.usage_id)
class DefinitionLocator(Locator): class DefinitionLocator(Locator):
...@@ -478,7 +515,7 @@ class DefinitionLocator(Locator): ...@@ -478,7 +515,7 @@ class DefinitionLocator(Locator):
Container for how to locate a description (the course-independent content). Container for how to locate a description (the course-independent content).
""" """
URL_RE = re.compile(r'^defx://' + URL_VERSION_PREFIX + '([^/]+)$', re.IGNORECASE) URL_RE = re.compile(r'^defx://' + VERSION_PREFIX + '([^/]+)$', re.IGNORECASE)
def __init__(self, definition_id): def __init__(self, definition_id):
if isinstance(definition_id, basestring): if isinstance(definition_id, basestring):
regex_match = self.URL_RE.match(definition_id) regex_match = self.URL_RE.match(definition_id)
...@@ -491,7 +528,7 @@ class DefinitionLocator(Locator): ...@@ -491,7 +528,7 @@ class DefinitionLocator(Locator):
Return a string representing this location. Return a string representing this location.
unicode(self) returns something like this: "version/519665f6223ebd6980884f2b" unicode(self) returns something like this: "version/519665f6223ebd6980884f2b"
''' '''
return URL_VERSION_PREFIX + str(self.definition_id) return VERSION_PREFIX + str(self.definition_id)
def url(self): def url(self):
""" """
......
import re import re
# Prefix for the branch portion of a locator URL # Prefix for the branch portion of a locator URL
BRANCH_PREFIX = "/branch/" BRANCH_PREFIX = r"branch/"
# Prefix for the block portion of a locator URL # Prefix for the block portion of a locator URL
BLOCK_PREFIX = "/block/" BLOCK_PREFIX = r"block/"
# Prefix for the version portion of a locator URL, when it is preceded by a course ID # Prefix for the version portion of a locator URL, when it is preceded by a course ID
VERSION_PREFIX = "/version/" VERSION_PREFIX = r"version/"
# Prefix for version when it begins the URL (no course ID).
URL_VERSION_PREFIX = 'version/'
URL_RE = re.compile(r'^(edx://)?(.+)$', re.IGNORECASE)
ALLOWED_ID_CHARS = r'[a-zA-Z0-9_\-~.]' ALLOWED_ID_CHARS = r'[a-zA-Z0-9_\-~.]'
URL_RE_SOURCE = r"""
(?P<tag>edx://)?
((?P<course_id>{ALLOWED_ID_CHARS}+)/?)?
({BRANCH_PREFIX}(?P<branch>{ALLOWED_ID_CHARS}+)/?)?
({VERSION_PREFIX}(?P<version_guid>[A-F0-9]+)/?)?
({BLOCK_PREFIX}(?P<block>{ALLOWED_ID_CHARS}+))?
""".format(
ALLOWED_ID_CHARS=ALLOWED_ID_CHARS, BRANCH_PREFIX=BRANCH_PREFIX,
VERSION_PREFIX=VERSION_PREFIX, BLOCK_PREFIX=BLOCK_PREFIX
)
URL_RE = re.compile('^' + URL_RE_SOURCE + '$', re.IGNORECASE | re.VERBOSE)
def parse_url(string, tag_optional=False): def parse_url(string, tag_optional=False):
""" """
A url usually begins with 'edx://' (case-insensitive match), A url usually begins with 'edx://' (case-insensitive match),
...@@ -21,9 +32,9 @@ def parse_url(string, tag_optional=False): ...@@ -21,9 +32,9 @@ def parse_url(string, tag_optional=False):
Examples: Examples:
'edx://version/0123FFFF' 'edx://version/0123FFFF'
'edx://mit.eecs.6002x' 'edx://mit.eecs.6002x'
'edx://mit.eecs.6002x;published' 'edx://mit.eecs.6002x/branch/published'
'edx://mit.eecs.6002x;published/block/HW3' 'edx://mit.eecs.6002x/branch/published/block/HW3'
'edx://mit.eecs.6002x;published/version/000eee12345/block/HW3' 'edx://mit.eecs.6002x/branch/published/version/000eee12345/block/HW3'
This returns None if string cannot be parsed. This returns None if string cannot be parsed.
...@@ -37,12 +48,10 @@ def parse_url(string, tag_optional=False): ...@@ -37,12 +48,10 @@ def parse_url(string, tag_optional=False):
match = URL_RE.match(string) match = URL_RE.match(string)
if not match: if not match:
return None return None
if match.group(1) is None and not tag_optional: matched_dict = match.groupdict()
if matched_dict['tag'] is None and not tag_optional:
return None return None
path = match.group(2) return matched_dict
if path.startswith(URL_VERSION_PREFIX):
return parse_guid(path[len(URL_VERSION_PREFIX):])
return parse_course_id(path)
BLOCK_RE = re.compile(r'^' + ALLOWED_ID_CHARS + r'+$', re.IGNORECASE) BLOCK_RE = re.compile(r'^' + ALLOWED_ID_CHARS + r'+$', re.IGNORECASE)
...@@ -60,34 +69,6 @@ def parse_block_ref(string): ...@@ -60,34 +69,6 @@ def parse_block_ref(string):
return None return None
GUID_RE = re.compile(
r'^(?P<version_guid>[A-F0-9]+)(' + BLOCK_PREFIX + '(?P<block>' + ALLOWED_ID_CHARS + r'+))?$',
re.IGNORECASE
)
def parse_guid(string):
"""
A version_guid is a string of hex digits (0-F).
If string is a version_guid, returns a dict with key 'version_guid' and the value,
otherwise returns None.
"""
m = GUID_RE.match(string)
if m is not None:
return m.groupdict()
else:
return None
COURSE_ID_RE = re.compile(
r'^(?P<id>' + ALLOWED_ID_CHARS + r'+)(' +
BRANCH_PREFIX + r'(?P<branch>' + ALLOWED_ID_CHARS + r'+))?(' +
VERSION_PREFIX + r'(?P<version_guid>[A-F0-9]+))?(' +
BLOCK_PREFIX + r'(?P<block>' + ALLOWED_ID_CHARS + r'+))?$', re.IGNORECASE
)
def parse_course_id(string): def parse_course_id(string):
r""" r"""
...@@ -122,7 +103,7 @@ def parse_course_id(string): ...@@ -122,7 +103,7 @@ def parse_course_id(string):
Block is optional: if missing returned_dict['block'] is None. Block is optional: if missing returned_dict['block'] is None.
Else returns None. Else returns None.
""" """
match = COURSE_ID_RE.match(string) match = URL_RE.match(string)
if not match: if not match:
return None return None
return match.groupdict() return match.groupdict()
...@@ -247,7 +247,8 @@ class TestLocationMapper(unittest.TestCase): ...@@ -247,7 +247,8 @@ class TestLocationMapper(unittest.TestCase):
new_style_course_id = '{}.geek_dept.{}.baz_run'.format(org, course) new_style_course_id = '{}.geek_dept.{}.baz_run'.format(org, course)
prob_locator = BlockUsageLocator( prob_locator = BlockUsageLocator(
course_id=new_style_course_id, course_id=new_style_course_id,
usage_id='problem2' usage_id='problem2',
branch='published'
) )
prob_location = loc_mapper().translate_locator_to_location(prob_locator) prob_location = loc_mapper().translate_locator_to_location(prob_locator)
self.assertIsNone(prob_location, 'found entry in empty map table') self.assertIsNone(prob_location, 'found entry in empty map table')
...@@ -265,7 +266,9 @@ class TestLocationMapper(unittest.TestCase): ...@@ -265,7 +266,9 @@ class TestLocationMapper(unittest.TestCase):
# default branch # default branch
self.assertEqual(prob_location, Location('i4x', org, course, 'problem', 'abc123', None)) self.assertEqual(prob_location, Location('i4x', org, course, 'problem', 'abc123', None))
# explicit branch # explicit branch
prob_locator = BlockUsageLocator(prob_locator, branch='draft') prob_locator = BlockUsageLocator(
course_id=prob_locator.course_id, branch='draft', usage_id=prob_locator.usage_id
)
prob_location = loc_mapper().translate_locator_to_location(prob_locator) prob_location = loc_mapper().translate_locator_to_location(prob_locator)
self.assertEqual(prob_location, Location('i4x', org, course, 'problem', 'abc123', 'draft')) self.assertEqual(prob_location, Location('i4x', org, course, 'problem', 'abc123', 'draft'))
prob_locator = BlockUsageLocator( prob_locator = BlockUsageLocator(
...@@ -276,12 +279,13 @@ class TestLocationMapper(unittest.TestCase): ...@@ -276,12 +279,13 @@ class TestLocationMapper(unittest.TestCase):
# same for chapter except chapter cannot be draft in old system # same for chapter except chapter cannot be draft in old system
chap_locator = BlockUsageLocator( chap_locator = BlockUsageLocator(
course_id=new_style_course_id, course_id=new_style_course_id,
usage_id='chapter48f' usage_id='chapter48f',
branch='production'
) )
chap_location = loc_mapper().translate_locator_to_location(chap_locator) chap_location = loc_mapper().translate_locator_to_location(chap_locator)
self.assertEqual(chap_location, Location('i4x', org, course, 'chapter', '48f23a10395384929234')) self.assertEqual(chap_location, Location('i4x', org, course, 'chapter', '48f23a10395384929234'))
# explicit branch # explicit branch
chap_locator = BlockUsageLocator(chap_locator, branch='draft') chap_locator.branch = 'draft'
chap_location = loc_mapper().translate_locator_to_location(chap_locator) chap_location = loc_mapper().translate_locator_to_location(chap_locator)
self.assertEqual(chap_location, Location('i4x', org, course, 'chapter', '48f23a10395384929234')) self.assertEqual(chap_location, Location('i4x', org, course, 'chapter', '48f23a10395384929234'))
chap_locator = BlockUsageLocator( chap_locator = BlockUsageLocator(
...@@ -382,7 +386,7 @@ class TestLocationMapper(unittest.TestCase): ...@@ -382,7 +386,7 @@ class TestLocationMapper(unittest.TestCase):
self.assertEqual(new_usage_id, new_usage_id2) self.assertEqual(new_usage_id, new_usage_id2)
# it should be in the distractor now # it should be in the distractor now
new_location = loc_mapper().translate_locator_to_location( new_location = loc_mapper().translate_locator_to_location(
BlockUsageLocator(course_id=new_style_course_id, usage_id=new_usage_id2) BlockUsageLocator(course_id=new_style_course_id, usage_id=new_usage_id2, branch='published')
) )
self.assertEqual(new_location, location) self.assertEqual(new_location, location)
# add one close to the existing chapter (cause name collision) # add one close to the existing chapter (cause name collision)
...@@ -391,11 +395,15 @@ class TestLocationMapper(unittest.TestCase): ...@@ -391,11 +395,15 @@ class TestLocationMapper(unittest.TestCase):
self.assertRegexpMatches(new_usage_id, r'^chapter48f\d') self.assertRegexpMatches(new_usage_id, r'^chapter48f\d')
# retrievable from both courses # retrievable from both courses
new_location = loc_mapper().translate_locator_to_location( new_location = loc_mapper().translate_locator_to_location(
BlockUsageLocator(course_id=new_style_course_id, usage_id=new_usage_id) BlockUsageLocator(course_id=new_style_course_id, usage_id=new_usage_id, branch='published')
) )
self.assertEqual(new_location, location) self.assertEqual(new_location, location)
new_location = loc_mapper().translate_locator_to_location( new_location = loc_mapper().translate_locator_to_location(
BlockUsageLocator(course_id='{}.{}.{}'.format(org, course, 'baz_run'), usage_id=new_usage_id) BlockUsageLocator(
course_id='{}.{}.{}'.format(org, course, 'baz_run'),
usage_id=new_usage_id,
branch='published'
)
) )
self.assertEqual(new_location, location) self.assertEqual(new_location, location)
...@@ -443,11 +451,11 @@ class TestLocationMapper(unittest.TestCase): ...@@ -443,11 +451,11 @@ class TestLocationMapper(unittest.TestCase):
# change in all courses to same value # change in all courses to same value
loc_mapper().update_block_location_translator(location, 'problem1') loc_mapper().update_block_location_translator(location, 'problem1')
trans_loc = loc_mapper().translate_locator_to_location( trans_loc = loc_mapper().translate_locator_to_location(
BlockUsageLocator(course_id=new_style_course_id, usage_id='problem1') BlockUsageLocator(course_id=new_style_course_id, usage_id='problem1', branch='published')
) )
self.assertEqual(trans_loc, location) self.assertEqual(trans_loc, location)
trans_loc = loc_mapper().translate_locator_to_location( trans_loc = loc_mapper().translate_locator_to_location(
BlockUsageLocator(course_id=new_style_course_id2, usage_id='problem1') BlockUsageLocator(course_id=new_style_course_id2, usage_id='problem1', branch='published')
) )
self.assertEqual(trans_loc, location) self.assertEqual(trans_loc, location)
# try to change to overwrite used usage_id # try to change to overwrite used usage_id
...@@ -457,12 +465,12 @@ class TestLocationMapper(unittest.TestCase): ...@@ -457,12 +465,12 @@ class TestLocationMapper(unittest.TestCase):
# just change the one course # just change the one course
loc_mapper().update_block_location_translator(location, 'chapter2', '{}/{}/{}'.format(org, course, 'baz_run')) loc_mapper().update_block_location_translator(location, 'chapter2', '{}/{}/{}'.format(org, course, 'baz_run'))
trans_loc = loc_mapper().translate_locator_to_location( trans_loc = loc_mapper().translate_locator_to_location(
BlockUsageLocator(course_id=new_style_course_id, usage_id='chapter2') BlockUsageLocator(course_id=new_style_course_id, usage_id='chapter2', branch='published')
) )
self.assertEqual(trans_loc.name, '48f23a10395384929234') self.assertEqual(trans_loc.name, '48f23a10395384929234')
# but this still points to the old # but this still points to the old
trans_loc = loc_mapper().translate_locator_to_location( trans_loc = loc_mapper().translate_locator_to_location(
BlockUsageLocator(course_id=new_style_course_id2, usage_id='chapter2') BlockUsageLocator(course_id=new_style_course_id2, usage_id='chapter2', branch='published')
) )
self.assertEqual(trans_loc.name, '1') self.assertEqual(trans_loc.name, '1')
...@@ -496,10 +504,10 @@ class TestLocationMapper(unittest.TestCase): ...@@ -496,10 +504,10 @@ class TestLocationMapper(unittest.TestCase):
# delete from all courses # delete from all courses
loc_mapper().delete_block_location_translator(location) loc_mapper().delete_block_location_translator(location)
self.assertIsNone(loc_mapper().translate_locator_to_location( self.assertIsNone(loc_mapper().translate_locator_to_location(
BlockUsageLocator(course_id=new_style_course_id, usage_id='problem1') BlockUsageLocator(course_id=new_style_course_id, usage_id='problem1', branch='published')
)) ))
self.assertIsNone(loc_mapper().translate_locator_to_location( self.assertIsNone(loc_mapper().translate_locator_to_location(
BlockUsageLocator(course_id=new_style_course_id2, usage_id='problem2') BlockUsageLocator(course_id=new_style_course_id2, usage_id='problem2', branch='published')
)) ))
# delete from one course # delete from one course
location = location.replace(name='abc123') location = location.replace(name='abc123')
......
...@@ -5,7 +5,7 @@ from unittest import TestCase ...@@ -5,7 +5,7 @@ from unittest import TestCase
from bson.objectid import ObjectId from bson.objectid import ObjectId
from xmodule.modulestore.locator import Locator, CourseLocator, BlockUsageLocator, DefinitionLocator from xmodule.modulestore.locator import Locator, CourseLocator, BlockUsageLocator, DefinitionLocator
from xmodule.modulestore.parsers import BRANCH_PREFIX, BLOCK_PREFIX, VERSION_PREFIX, URL_VERSION_PREFIX from xmodule.modulestore.parsers import BRANCH_PREFIX, BLOCK_PREFIX, VERSION_PREFIX
from xmodule.modulestore.exceptions import InsufficientSpecificationError, OverSpecificationError from xmodule.modulestore.exceptions import InsufficientSpecificationError, OverSpecificationError
from xmodule.modulestore import Location from xmodule.modulestore import Location
import random import random
...@@ -36,12 +36,12 @@ class LocatorTest(TestCase): ...@@ -36,12 +36,12 @@ class LocatorTest(TestCase):
self.assertRaises( self.assertRaises(
OverSpecificationError, OverSpecificationError,
CourseLocator, CourseLocator,
url='edx://mit.eecs.6002x' + BRANCH_PREFIX + 'published', url='edx://mit.eecs.6002x/' + BRANCH_PREFIX + 'published',
branch='draft') branch='draft')
self.assertRaises( self.assertRaises(
OverSpecificationError, OverSpecificationError,
CourseLocator, CourseLocator,
course_id='mit.eecs.6002x' + BRANCH_PREFIX + 'published', course_id='mit.eecs.6002x/' + BRANCH_PREFIX + 'published',
branch='draft') branch='draft')
def test_course_constructor_underspecified(self): def test_course_constructor_underspecified(self):
...@@ -59,8 +59,8 @@ class LocatorTest(TestCase): ...@@ -59,8 +59,8 @@ class LocatorTest(TestCase):
testobj_1 = CourseLocator(version_guid=test_id_1) testobj_1 = CourseLocator(version_guid=test_id_1)
self.check_course_locn_fields(testobj_1, 'version_guid', version_guid=test_id_1) self.check_course_locn_fields(testobj_1, 'version_guid', version_guid=test_id_1)
self.assertEqual(str(testobj_1.version_guid), test_id_1_loc) self.assertEqual(str(testobj_1.version_guid), test_id_1_loc)
self.assertEqual(str(testobj_1), URL_VERSION_PREFIX + test_id_1_loc) self.assertEqual(str(testobj_1), VERSION_PREFIX + test_id_1_loc)
self.assertEqual(testobj_1.url(), 'edx://' + URL_VERSION_PREFIX + test_id_1_loc) self.assertEqual(testobj_1.url(), 'edx://' + VERSION_PREFIX + test_id_1_loc)
# Test using a given string # Test using a given string
test_id_2_loc = '519665f6223ebd6980884f2b' test_id_2_loc = '519665f6223ebd6980884f2b'
...@@ -68,8 +68,8 @@ class LocatorTest(TestCase): ...@@ -68,8 +68,8 @@ class LocatorTest(TestCase):
testobj_2 = CourseLocator(version_guid=test_id_2) testobj_2 = CourseLocator(version_guid=test_id_2)
self.check_course_locn_fields(testobj_2, 'version_guid', version_guid=test_id_2) self.check_course_locn_fields(testobj_2, 'version_guid', version_guid=test_id_2)
self.assertEqual(str(testobj_2.version_guid), test_id_2_loc) self.assertEqual(str(testobj_2.version_guid), test_id_2_loc)
self.assertEqual(str(testobj_2), URL_VERSION_PREFIX + test_id_2_loc) self.assertEqual(str(testobj_2), VERSION_PREFIX + test_id_2_loc)
self.assertEqual(testobj_2.url(), 'edx://' + URL_VERSION_PREFIX + test_id_2_loc) self.assertEqual(testobj_2.url(), 'edx://' + VERSION_PREFIX + test_id_2_loc)
def test_course_constructor_bad_course_id(self): def test_course_constructor_bad_course_id(self):
""" """
...@@ -77,19 +77,19 @@ class LocatorTest(TestCase): ...@@ -77,19 +77,19 @@ class LocatorTest(TestCase):
""" """
for bad_id in (' mit.eecs', for bad_id in (' mit.eecs',
'mit.eecs ', 'mit.eecs ',
URL_VERSION_PREFIX + 'mit.eecs', VERSION_PREFIX + 'mit.eecs',
BLOCK_PREFIX + 'block/mit.eecs', BLOCK_PREFIX + 'black/mit.eecs',
'mit.ee cs', 'mit.ee cs',
'mit.ee,cs', 'mit.ee,cs',
'mit.ee/cs', 'mit.ee/cs',
'mit.ee&cs', 'mit.ee&cs',
'mit.ee()cs', 'mit.ee()cs',
BRANCH_PREFIX + 'this', BRANCH_PREFIX + 'this',
'mit.eecs' + BRANCH_PREFIX, 'mit.eecs/' + BRANCH_PREFIX,
'mit.eecs' + BRANCH_PREFIX + 'this' + BRANCH_PREFIX + 'that', 'mit.eecs/' + BRANCH_PREFIX + 'this/' + BRANCH_PREFIX + 'that',
'mit.eecs' + BRANCH_PREFIX + 'this' + BRANCH_PREFIX, 'mit.eecs/' + BRANCH_PREFIX + 'this/' + BRANCH_PREFIX,
'mit.eecs' + BRANCH_PREFIX + 'this ', 'mit.eecs/' + BRANCH_PREFIX + 'this ',
'mit.eecs' + BRANCH_PREFIX + 'th%is ', 'mit.eecs/' + BRANCH_PREFIX + 'th%is ',
): ):
self.assertRaises(ValueError, CourseLocator, course_id=bad_id) self.assertRaises(ValueError, CourseLocator, course_id=bad_id)
self.assertRaises(ValueError, CourseLocator, url='edx://' + bad_id) self.assertRaises(ValueError, CourseLocator, url='edx://' + bad_id)
...@@ -107,7 +107,7 @@ class LocatorTest(TestCase): ...@@ -107,7 +107,7 @@ class LocatorTest(TestCase):
self.check_course_locn_fields(testobj, 'course_id', course_id=testurn) self.check_course_locn_fields(testobj, 'course_id', course_id=testurn)
def test_course_constructor_redundant_002(self): def test_course_constructor_redundant_002(self):
testurn = 'mit.eecs.6002x' + BRANCH_PREFIX + 'published' testurn = 'mit.eecs.6002x/' + BRANCH_PREFIX + 'published'
expected_urn = 'mit.eecs.6002x' expected_urn = 'mit.eecs.6002x'
expected_rev = 'published' expected_rev = 'published'
testobj = CourseLocator(course_id=testurn, url='edx://' + testurn) testobj = CourseLocator(course_id=testurn, url='edx://' + testurn)
...@@ -119,7 +119,7 @@ class LocatorTest(TestCase): ...@@ -119,7 +119,7 @@ class LocatorTest(TestCase):
# Test parsing a url when it starts with a version ID and there is also a block ID. # Test parsing a url when it starts with a version ID and there is also a block ID.
# This hits the parsers parse_guid method. # This hits the parsers parse_guid method.
test_id_loc = '519665f6223ebd6980884f2b' test_id_loc = '519665f6223ebd6980884f2b'
testobj = CourseLocator(url="edx://" + URL_VERSION_PREFIX + test_id_loc + BLOCK_PREFIX + "hw3") testobj = CourseLocator(url="edx://{}{}/{}hw3".format(VERSION_PREFIX, test_id_loc, BLOCK_PREFIX))
self.check_course_locn_fields( self.check_course_locn_fields(
testobj, testobj,
'test_block constructor', 'test_block constructor',
...@@ -128,14 +128,14 @@ class LocatorTest(TestCase): ...@@ -128,14 +128,14 @@ class LocatorTest(TestCase):
def test_course_constructor_url_course_id_and_version_guid(self): def test_course_constructor_url_course_id_and_version_guid(self):
test_id_loc = '519665f6223ebd6980884f2b' test_id_loc = '519665f6223ebd6980884f2b'
testobj = CourseLocator(url='edx://mit.eecs-honors.6002x' + VERSION_PREFIX + test_id_loc) testobj = CourseLocator(url='edx://mit.eecs-honors.6002x/' + VERSION_PREFIX + test_id_loc)
self.check_course_locn_fields(testobj, 'error parsing url with both course ID and version GUID', self.check_course_locn_fields(testobj, 'error parsing url with both course ID and version GUID',
course_id='mit.eecs-honors.6002x', course_id='mit.eecs-honors.6002x',
version_guid=ObjectId(test_id_loc)) version_guid=ObjectId(test_id_loc))
def test_course_constructor_url_course_id_branch_and_version_guid(self): def test_course_constructor_url_course_id_branch_and_version_guid(self):
test_id_loc = '519665f6223ebd6980884f2b' test_id_loc = '519665f6223ebd6980884f2b'
testobj = CourseLocator(url='edx://mit.eecs.~6002x' + BRANCH_PREFIX + 'draft-1' + VERSION_PREFIX + test_id_loc) testobj = CourseLocator(url='edx://mit.eecs.~6002x/' + BRANCH_PREFIX + 'draft-1/' + VERSION_PREFIX + test_id_loc)
self.check_course_locn_fields(testobj, 'error parsing url with both course ID branch, and version GUID', self.check_course_locn_fields(testobj, 'error parsing url with both course ID branch, and version GUID',
course_id='mit.eecs.~6002x', course_id='mit.eecs.~6002x',
branch='draft-1', branch='draft-1',
...@@ -150,7 +150,7 @@ class LocatorTest(TestCase): ...@@ -150,7 +150,7 @@ class LocatorTest(TestCase):
self.assertEqual(testobj.url(), 'edx://' + testurn) self.assertEqual(testobj.url(), 'edx://' + testurn)
def test_course_constructor_course_id_with_branch(self): def test_course_constructor_course_id_with_branch(self):
testurn = 'mit.eecs.6002x' + BRANCH_PREFIX + 'published' testurn = 'mit.eecs.6002x/' + BRANCH_PREFIX + 'published'
expected_id = 'mit.eecs.6002x' expected_id = 'mit.eecs.6002x'
expected_branch = 'published' expected_branch = 'published'
testobj = CourseLocator(course_id=testurn) testobj = CourseLocator(course_id=testurn)
...@@ -166,7 +166,7 @@ class LocatorTest(TestCase): ...@@ -166,7 +166,7 @@ class LocatorTest(TestCase):
def test_course_constructor_course_id_separate_branch(self): def test_course_constructor_course_id_separate_branch(self):
test_id = 'mit.eecs.6002x' test_id = 'mit.eecs.6002x'
test_branch = 'published' test_branch = 'published'
expected_urn = 'mit.eecs.6002x' + BRANCH_PREFIX + 'published' expected_urn = 'mit.eecs.6002x/' + BRANCH_PREFIX + 'published'
testobj = CourseLocator(course_id=test_id, branch=test_branch) testobj = CourseLocator(course_id=test_id, branch=test_branch)
self.check_course_locn_fields(testobj, 'course_id with separate branch', self.check_course_locn_fields(testobj, 'course_id with separate branch',
course_id=test_id, course_id=test_id,
...@@ -181,7 +181,7 @@ class LocatorTest(TestCase): ...@@ -181,7 +181,7 @@ class LocatorTest(TestCase):
""" """
The same branch appears in the course_id and the branch field. The same branch appears in the course_id and the branch field.
""" """
test_id = 'mit.eecs.6002x' + BRANCH_PREFIX + 'published' test_id = 'mit.eecs.6002x/' + BRANCH_PREFIX + 'published'
test_branch = 'published' test_branch = 'published'
expected_id = 'mit.eecs.6002x' expected_id = 'mit.eecs.6002x'
expected_urn = test_id expected_urn = test_id
...@@ -196,7 +196,7 @@ class LocatorTest(TestCase): ...@@ -196,7 +196,7 @@ class LocatorTest(TestCase):
self.assertEqual(testobj.url(), 'edx://' + expected_urn) self.assertEqual(testobj.url(), 'edx://' + expected_urn)
def test_block_constructor(self): def test_block_constructor(self):
testurn = 'mit.eecs.6002x' + BRANCH_PREFIX + 'published' + BLOCK_PREFIX + 'HW3' testurn = 'mit.eecs.6002x/' + BRANCH_PREFIX + 'published/' + BLOCK_PREFIX + 'HW3'
expected_id = 'mit.eecs.6002x' expected_id = 'mit.eecs.6002x'
expected_branch = 'published' expected_branch = 'published'
expected_block_ref = 'HW3' expected_block_ref = 'HW3'
...@@ -217,7 +217,7 @@ class LocatorTest(TestCase): ...@@ -217,7 +217,7 @@ class LocatorTest(TestCase):
def test_block_constructor_url_version_prefix(self): def test_block_constructor_url_version_prefix(self):
test_id_loc = '519665f6223ebd6980884f2b' test_id_loc = '519665f6223ebd6980884f2b'
testobj = BlockUsageLocator( testobj = BlockUsageLocator(
url='edx://mit.eecs.6002x' + VERSION_PREFIX + test_id_loc + BLOCK_PREFIX + 'lab2' url='edx://mit.eecs.6002x/{}{}/{}lab2'.format(VERSION_PREFIX, test_id_loc, BLOCK_PREFIX)
) )
self.check_block_locn_fields( self.check_block_locn_fields(
testobj, 'error parsing URL with version and block', testobj, 'error parsing URL with version and block',
...@@ -237,7 +237,9 @@ class LocatorTest(TestCase): ...@@ -237,7 +237,9 @@ class LocatorTest(TestCase):
def test_block_constructor_url_kitchen_sink(self): def test_block_constructor_url_kitchen_sink(self):
test_id_loc = '519665f6223ebd6980884f2b' test_id_loc = '519665f6223ebd6980884f2b'
testobj = BlockUsageLocator( testobj = BlockUsageLocator(
url='edx://mit.eecs.6002x' + BRANCH_PREFIX + 'draft' + VERSION_PREFIX + test_id_loc + BLOCK_PREFIX + 'lab2' url='edx://mit.eecs.6002x/{}draft/{}{}/{}lab2'.format(
BRANCH_PREFIX, VERSION_PREFIX, test_id_loc, BLOCK_PREFIX
)
) )
self.check_block_locn_fields( self.check_block_locn_fields(
testobj, 'error parsing URL with branch, version, and block', testobj, 'error parsing URL with branch, version, and block',
...@@ -248,7 +250,7 @@ class LocatorTest(TestCase): ...@@ -248,7 +250,7 @@ class LocatorTest(TestCase):
) )
def test_repr(self): def test_repr(self):
testurn = 'mit.eecs.6002x' + BRANCH_PREFIX + 'published' + BLOCK_PREFIX + 'HW3' testurn = 'mit.eecs.6002x/' + BRANCH_PREFIX + 'published/' + BLOCK_PREFIX + 'HW3'
testobj = BlockUsageLocator(course_id=testurn) testobj = BlockUsageLocator(course_id=testurn)
self.assertEqual('BlockUsageLocator("mit.eecs.6002x/branch/published/block/HW3")', repr(testobj)) self.assertEqual('BlockUsageLocator("mit.eecs.6002x/branch/published/block/HW3")', repr(testobj))
...@@ -286,7 +288,7 @@ class LocatorTest(TestCase): ...@@ -286,7 +288,7 @@ class LocatorTest(TestCase):
def test_description_locator_url(self): def test_description_locator_url(self):
object_id = '{:024x}'.format(random.randrange(16 ** 24)) object_id = '{:024x}'.format(random.randrange(16 ** 24))
definition_locator = DefinitionLocator(object_id) definition_locator = DefinitionLocator(object_id)
self.assertEqual('defx://' + URL_VERSION_PREFIX + object_id, definition_locator.url()) self.assertEqual('defx://' + VERSION_PREFIX + object_id, definition_locator.url())
self.assertEqual(definition_locator, DefinitionLocator(definition_locator.url())) self.assertEqual(definition_locator, DefinitionLocator(definition_locator.url()))
def test_description_locator_version(self): def test_description_locator_version(self):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment