[cc-commits] [SCM] cc.api based on sane cc.license (branch master) updated. 5a3909b1021f3b40e628b77726c9db5e8c46ed32
git version control
git at a7.creativecommons.org
Fri Mar 12 21:18:43 EST 2010
The branch, master has been updated
via 5a3909b1021f3b40e628b77726c9db5e8c46ed32 (commit)
via 8dd8f82521c44f2539ac32de209d3daca89bd338 (commit)
via 588bdbf301913700f33a4c2feb1af73f996dfb53 (commit)
via ef05168814c2cb1e18abe4e4bd6713c3b494184a (commit)
via 26e44a632bdd52c48b67855ae5cc456c375cc035 (commit)
via 51ff96273576c318c2da940380ac385a1cfc2ed7 (commit)
via 1ffe524108a3ddf7d55751e3bf06dfebcdd19999 (commit)
via 928f860215c70c088777f48be80a57063e187249 (commit)
via 376136e57f8afb6a1b28495690dc1dd839666366 (commit)
via 438f9d1ed2ea239f06ffd71f2750ef5ec336c1bd (commit)
from ab823db1624214b4594c0a5f121eeba2f74c51c0 (commit)
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 5a3909b1021f3b40e628b77726c9db5e8c46ed32
Author: John Doig <jed at jedpad.(none)>
Date: Fri Mar 12 18:18:29 2010 -0800
This little change enables 100% test coverage in cc.api!
commit 8dd8f82521c44f2539ac32de209d3daca89bd338
Author: John Doig <jed at jedpad.(none)>
Date: Fri Mar 12 18:17:42 2010 -0800
cc.license upgrade is unstable, freeze at versions specified in setup.py
cc.license will also throw an unhandled KeyError in CCoHTMLFormatter that is handled here by returning a pythonerr error message to the user
commit 588bdbf301913700f33a4c2feb1af73f996dfb53
Author: John Doig <jed at jedpad.(none)>
Date: Fri Mar 12 18:16:02 2010 -0800
Need to support dc types --> work_dict key equivalencies in cc.license
Also includes a minor code refac/reordering
commit ef05168814c2cb1e18abe4e4bd6713c3b494184a
Author: John Doig <jed at jedpad.(none)>
Date: Fri Mar 12 18:14:32 2010 -0800
Adding test cases for work-info logic
commit 26e44a632bdd52c48b67855ae5cc456c375cc035
Author: John Doig <jed at jedpad.(none)>
Date: Fri Mar 12 18:13:53 2010 -0800
Don't hardcode the encoding in the xml declarations
commit 51ff96273576c318c2da940380ac385a1cfc2ed7
Author: John Doig <jed at jedpad.(none)>
Date: Fri Mar 12 18:13:06 2010 -0800
Let web.py handle the 405 errors
commit 1ffe524108a3ddf7d55751e3bf06dfebcdd19999
Author: John Doig <jed at jedpad.(none)>
Date: Fri Mar 12 11:06:05 2010 -0800
Refactoring js renderings and adding test cases for when select tags are used and formatted in js
commit 928f860215c70c088777f48be80a57063e187249
Author: John Doig <jed at jedpad.(none)>
Date: Thu Mar 11 18:18:49 2010 -0800
Adding test cases for Handler classes
commit 376136e57f8afb6a1b28495690dc1dd839666366
Author: John Doig <jed at jedpad.(none)>
Date: Thu Mar 11 17:54:35 2010 -0800
Need to return 500 response for all internal errors
commit 438f9d1ed2ea239f06ffd71f2750ef5ec336c1bd
Author: John Doig <jed at jedpad.(none)>
Date: Thu Mar 11 17:53:57 2010 -0800
Adding work-info functionality to issue and get
-----------------------------------------------------------------------
Summary of changes (followed by patch):
cc/api/api_exceptions.py | 5 -
cc/api/app.py | 4 +-
cc/api/handlers.py | 13 ++-
cc/api/resources/license.py | 30 ++++----
cc/api/resources/simple.py | 57 ++++++++------
cc/api/resources/support.py | 34 +++++----
cc/api/support.py | 99 +++++++++++++++++-------
cc/api/tests/test_handlers.py | 168 +++++++++++++++++++++++++++++++++++++++++
cc/api/tests/test_license.py | 105 +++++++++++++++++++++++++-
cc/api/tests/test_simple.py | 29 ++++++-
cc/api/tests/test_support.py | 28 ++++++-
setup.py | 3 +-
12 files changed, 469 insertions(+), 106 deletions(-)
create mode 100644 cc/api/tests/test_handlers.py
diff --git a/cc/api/api_exceptions.py b/cc/api/api_exceptions.py
index 805f8af..8c60a4a 100644
--- a/cc/api/api_exceptions.py
+++ b/cc/api/api_exceptions.py
@@ -41,8 +41,3 @@ def pythonerr():
def missingparam(param):
return _error_tree('missingparam',
'A value for %s must be supplied.' % param )
-
-def invalidmethod(*methods):
- return _error_tree('invalidmethod',
- 'This resource only supports the %s method%s.' % \
- (','.join(methods), (len(methods) > 1 and 's' or '')))
diff --git a/cc/api/app.py b/cc/api/app.py
index 46296fd..b4bd8e4 100644
--- a/cc/api/app.py
+++ b/cc/api/app.py
@@ -42,5 +42,5 @@ urls = ( # tuple of url to resource method mappings
application = web.application(urls, globals(),)
-if __name__ == "__main__":
- application.run()
+if __name__ == "__main__": application.run()
+
diff --git a/cc/api/handlers.py b/cc/api/handlers.py
index 09e93b6..344969c 100644
--- a/cc/api/handlers.py
+++ b/cc/api/handlers.py
@@ -75,7 +75,7 @@ class XMLHandler(Handler):
def response(self, results):
""" serialize the results ElementTree object """
- sig = '<?xml version="1.0" encoding="utf-8"?>\n'
+ sig = '<?xml version="1.0" encoding="%s"?>\n' % self.encoding
if not ET.iselement(results):
return sig + ''.join([
ET.tostring(r,pretty_print=True) for r in results])
@@ -117,11 +117,11 @@ def render_as(*types):
supported_types.extend([ t for t in h.content_types ])
except KeyError, e:
- raise e, "Invalid type used in content_types decorator."
+ return web.internalerror()
# use mimeparse to parse the http Accept header string
# restrict matches to the content types supported
- if http_accept is not None:
+ if http_accept:
mime_type = mimeparse.best_match(supported_types, http_accept)
if mime_type == '':
@@ -136,11 +136,14 @@ def render_as(*types):
# throw an exception. if the resource resulted in anything
# other than an xml tree then this decorator should not be
# used for that resource's method.
+ try:
+ result = fn(*args, **kwargs)
+ except Exception, e:
+ return web.internalerror()
- return handler().response(fn(*args, **kwargs))
+ return handler().response(result)
except TypeError, e:
- print e
# this decorator is only good for results that are serializable
return web.internalerror()
diff --git a/cc/api/resources/license.py b/cc/api/resources/license.py
index e936a90..08df2dd 100644
--- a/cc/api/resources/license.py
+++ b/cc/api/resources/license.py
@@ -99,7 +99,6 @@ class issue:
except AssertionError, e:
return api_exceptions.invalidanswer()
-
answers_dict = support.build_answers_dict(lclass, answers)
locale = 'en'
@@ -119,8 +118,11 @@ class issue:
# issue the answers dict to the cc.license selector
license = lclass.by_answers(answers_dict)
-
- return support.build_results_tree(license, work_dict, locale)
+
+ try:
+ return support.build_results_tree(license, work_dict, locale)
+ except:
+ return api_exceptions.pythonerr()
class issue_get:
@render_as('xml')
@@ -135,25 +137,21 @@ class issue_get:
lclass = cc.license.selectors.choose(selector)
except cc.license.CCLicenseError:
return api_exceptions.invalidclass()
-
- # build an answers xml tree
- answers = ET.Element('answers')
- questions = ET.SubElement(answers, 'license-%s' % selector)
-
- for question in lclass.questions():
- default_answer = question.answers()[0][1]
- ET.SubElement(questions, question.id).text = \
- str(args.get(question.id, default_answer))
+ answers = support.build_answers_xml(lclass, args)
+
try:
support.validate_answers(lclass, answers)
except AssertionError, e:
- raise e
+ return api_exceptions.invalidanswer()
answers_dict = support.build_answers_dict(lclass, answers)
-
- # build the work_dict
+ work_dict = support.build_work_dict(answers)
issued_license = lclass.by_answers(answers_dict)
- return support.build_results_tree(issued_license, {}, locale)
+ try:
+ return support.build_results_tree(issued_license, work_dict, locale)
+ except:
+ return api_exceptions.pythonerr()
+
diff --git a/cc/api/resources/simple.py b/cc/api/resources/simple.py
index 1ff7b19..94fcf25 100644
--- a/cc/api/resources/simple.py
+++ b/cc/api/resources/simple.py
@@ -25,23 +25,15 @@ import lxml.etree as ET
from cc.api.handlers import render_as, content_type
-def chooser_dropdown():
- # collect query string parameters
- jurisdiction = web.input().get('jurisdiction', None)
- exclude = web.input().get('exclude', [])
- locale = web.input().get('locale', 'en')
- select = web.input().get('select', None)
-
- # ensure exclude is a list
- if type(exclude) != list: exclude = [exclude]
-
+def chooser_dropdown(jurisdiction, exclude, locale, select=None):
+
codes = ['by', 'by-sa', 'by-nc', 'by-nd', 'by-nc-nd', 'publicdomain']
options = []
for code in codes:
l = cc.license.by_code(code, jurisdiction) # fails on CC0 :/
- if l.uri in exclude:
- continue
+ if filter(lambda e: e in l.uri, exclude):
+ continue # license url contains a string found in exclude list
option = ET.Element('option', dict(value=l.uri))
option.text = l.title(locale)
options.append(option)
@@ -64,23 +56,40 @@ class chooser:
@render_as('html')
def GET(self):
""" done this way so that the xml logic isn't serialized """
- return chooser_dropdown()
+ # collect query string parameters
+ jurisdiction = web.input().get('jurisdiction', None)
+ exclude = web.input().get('exclude', [])
+ locale = web.input().get('locale', 'en')
+ select = web.input().get('select', None)
+ # ensure exclude is a list
+ if type(exclude) != list: exclude = [exclude]
+
+ return chooser_dropdown(jurisdiction, exclude, locale, select)
class chooser_js:
@content_type('text/plain')
def GET(self):
-
- html = chooser_dropdown()
-
+
+ # collect query string parameters
+ jurisdiction = web.input().get('jurisdiction', None)
+ exclude = web.input().get('exclude', [])
+ locale = web.input().get('locale', 'en')
+ select = web.input().get('select', None)
+
+ # ensure exclude is a list
+ if type(exclude) != list: exclude = [exclude]
+
+ html = chooser_dropdown(jurisdiction, exclude, locale, select)
+
# is there a root <select> tag included
if ET.iselement(html):
- # this doesn't feel right, but it works all the same...
- # ignore the last element which will always be an empty string
- select = ET.tostring(html, pretty_print=True).split('\n')[:-1]
- for node in select:
- yield "document.write('%s');\n" % node.strip() # remove padding
+ yield "document.write('<select name=\"%s\">');\n" % \
+ html.attrib['name']
- else:
- for node in html:
- yield "document.write('%s');\n" % ET.tostring(node)
+ for node in html:
+ yield "document.write('%s');\n" % ET.tostring(node)
+
+ if ET.iselement(html):
+ yield "document.write('</select>');"
+ return
diff --git a/cc/api/resources/support.py b/cc/api/resources/support.py
index b2d3efa..84288fa 100644
--- a/cc/api/resources/support.py
+++ b/cc/api/resources/support.py
@@ -24,15 +24,11 @@ import lxml.etree as ET
from cc.api.handlers import render_as, content_type
-def jurisdictions_dropdown():
+def jurisdictions_dropdown(locale, select=None):
""" Returns a list of html option element as lxml Element objects.
If a select tag is specified, an lxml ElementTree is returned where the
root is an html select tag with options elements as its children. """
- # collect query string arguments
- locale = web.input().get('locale', 'en')
- select = web.input().get('select', None)
-
options = []
juri_selector = 'http://creativecommons.org/international/%s/'
for juri in cc.license.jurisdictions.list():
@@ -54,27 +50,35 @@ class jurisdictions:
@render_as('html')
def GET(self):
- return jurisdictions_dropdown()
+ # collect query string arguments
+ locale = web.input().get('locale', 'en')
+ select = web.input().get('select', None)
+
+ return jurisdictions_dropdown(locale, select)
class jurisdictions_js:
@content_type('text/plain')
def GET(self):
+ # collect query string arguments
+ locale = web.input().get('locale', 'en')
+ select = web.input().get('select', None)
- html = jurisdictions_dropdown()
+ html = jurisdictions_dropdown(locale, select)
# is there a root <select> tag included
if ET.iselement(html):
- # this doesn't feel right, but it works all the same...
- # ignore the last element which will always be an empty string
- select = ET.tostring(html, pretty_print=True).split('\n')[:-1]
- for node in select:
- yield "document.write('%s');\n" % node.strip() # remove padding
+ yield "document.write('<select name=\"%s\">');\n" % \
+ html.attrib['name']
- else:
- for node in html:
- yield "document.write('%s');\n" % ET.tostring(node)
+ for node in html:
+ yield "document.write('%s');\n" % ET.tostring(node)
+ if ET.iselement(html):
+ yield "document.write('</select>');"
+
+ return
+
diff --git a/cc/api/support.py b/cc/api/support.py
index 5beef3c..ba8c339 100644
--- a/cc/api/support.py
+++ b/cc/api/support.py
@@ -26,13 +26,27 @@ from copy import deepcopy
import cc.license
from cc.license.formatters.classes import HTMLFormatter, CC0HTMLFormatter
+HTML_FORMATTER_KEYS = {
+ 'source-url': 'source_work',
+ 'type': 'format',
+ 'title': 'attribution_name',
+ 'attribution_name': 'attribution_name',
+ 'attribution_url' : 'attribution_url',
+ 'more_permissions_url': 'more_permissions_url',
+ }
+CC0_FORMATTER_KEYS = {
+ 'title': 'work_title',
+ 'actor_href': 'actor_href',
+ 'work_jurisdiction': 'work_jurisdiction',
+ 'name': 'name',
+ }
+
def validate_answers(selector, answers):
assert answers.xpath('/answers/license-%s' % selector.id), \
'license-class required'
answers = answers.xpath('/answers/license-%s' % selector.id)[0]
-
# don't allow answers to irrelevant questions for this class (except juri)
for answer in answers:
assert answer.tag in [ q.id for q in selector.questions() ] or \
@@ -44,14 +58,13 @@ def validate_answers(selector, answers):
answer = answers.xpath(question.id)[0]
valid_answers = [ v for l,v,d in question.answers() ]
-
if question.id == 'jurisdiction' and answer.text not in valid_answers:
answer.text = '' # silently fall back to Unported
assert answer.text in valid_answers, "%s not in [%s]." % \
(answer.text, ','.join(valid_answers))
- return
+ return True
def build_answers_dict(selector, answers):
# builds answer dict thats usable in cc.license
@@ -62,45 +75,71 @@ def build_answers_dict(selector, answers):
for field in selector.questions():
answers_dict[field.id] = questions.xpath(field.id)[0].text
- if answers.xpath('/answers/work-info'):
- # build work-info dict
- pass
-
return answers_dict
-def ambiguous_work_dict(work_dict):
- """ There is some ambiguity to the key names of the work_dicts, use this
- function to get around those inconsistencies. e.g. title (Standard
- formatter uses the 'worktitle' key, CC0 uses 'work_title', API <answers>
- string uses 'title' """
-
- if 'title' in work_dict.keys():
- # don't overwrite the other keys if they exist
- work_dict.setdefault('worktitle', work_dict['title'])
- work_dict.setdefault('work_title', work_dict['title'])
+def build_answers_xml(selector, args):
+ # build an answers xml tree
+ answers = ET.Element('answers')
+ questions = ET.SubElement(answers, 'license-%s' % selector.id)
+
+ # build the required answer elements
+ for question in selector.questions():
+ default_answer = question.answers()[0][1]
+ ET.SubElement(questions, question.id).text = \
+ str(args.get(question.id, default_answer))
+
+ # shove anything else in the args dict into the work-info
+ work_info = ET.SubElement(answers, 'work-info')
+ for field in args:
+ if questions.xpath('%s' % field) == [] and \
+ answers.xpath('%s' % field) == []:
+ # append an element for this argument
+ ET.SubElement(work_info, field).text = args[field]
+
+ return answers
+
+def build_work_dict(answers):
+ work_dict = {}
+
+ if not answers.xpath('/answers/work-info') or \
+ len(answers.xpath('/answers/work-info')[0]) == 0:
+ return work_dict
+
+ work_info = answers.xpath('/answers/work-info')[0]
+ if answers.xpath('/answers/license-zero'):
+ formatter_keys = CC0_FORMATTER_KEYS
+ else:
+ formatter_keys = HTML_FORMATTER_KEYS
+
+ for item in work_info:
+ if item.tag in formatter_keys.keys():
+ work_dict[ formatter_keys[item.tag] ] = item.text
+
return work_dict
def build_results_tree(license, work_dict=None, locale='en'):
- if license.license_code == 'CC0':
- formatter = CC0HTMLFormatter
- else: # more common case
- formatter = HTMLFormatter
-
root = ET.Element('result')
# add the license uri and name
- ET.SubElement(root, 'license-uri').text = unicode(license.uri)
- ET.SubElement(root, 'license-name').text = unicode(license.title(locale))
+ ET.SubElement(root, 'license-uri').text = license.uri
+ ET.SubElement(root, 'license-name').text = license.title(locale)
# parse the RDF and RDFa from cc.license
license_rdf = ET.parse( StringIO(license.rdf) )
-
- # support various arguments keys in the work_dict
- if work_dict: work_dict = ambiguous_work_dict(work_dict)
-
+
# prepare the RDFa for xml parsing
- rdfa = formatter().format(license, work_dict, locale)
+ try:
+ if license.license_code == 'CC0':
+ rdfa = CC0HTMLFormatter().format(license, work_dict, locale)
+ else:
+ rdfa = HTMLFormatter().format(license, work_dict, locale)
+ except Exception,e:
+ # cc.license formatter choked on something
+ # such an example would be an invalid work_jurisdiction value passed
+ # to the CC0 formatter
+ raise e
+
license_rdfa = ET.parse(StringIO('<p>%s</p>' % rdfa))
# build a Work tree
@@ -108,7 +147,7 @@ def build_results_tree(license, work_dict=None, locale='en'):
work = ET.Element('Work', { rdfns('about') : '' })
ET.SubElement(work, 'license', { rdfns('resource') : license.uri })
license_rdf.getroot().insert(0, work)
-
+
# add RDF trees to the results
ET.SubElement(root, 'rdf').append(license_rdf.getroot())
license_rdf = deepcopy(license_rdf)
diff --git a/cc/api/tests/test_handlers.py b/cc/api/tests/test_handlers.py
new file mode 100644
index 0000000..1f16d54
--- /dev/null
+++ b/cc/api/tests/test_handlers.py
@@ -0,0 +1,168 @@
+import os
+
+from cc.api.tests.test_common import *
+from webtest import TestApp
+
+import web
+import webtest
+import lxml.etree as ET
+
+from cc.api import handlers
+
+# list of registered handlers
+handler_classes = handlers.HandlerClass.handlers.values()
+
+# TODO produce the following classes in a generator
+
+class html:
+ @handlers.render_as('html')
+ def GET(self):
+ return ET.Element('result')
+
+class xml:
+ @handlers.render_as('xml')
+ def GET(self):
+ return ET.Element('result')
+
+class text:
+ @handlers.render_as('text')
+ def GET(self):
+ return ET.Element('result')
+
+class xml_or_html:
+ @handlers.render_as('xml', 'html')
+ def GET(self):
+ return ET.Element('result')
+
+class xml_list:
+ @handlers.render_as('text')
+ def GET(self):
+ return [ ET.Element(char) for char in 'awesome' ]
+
+class invalid_type:
+ @handlers.render_as('invalid')
+ def GET(self):
+ return ET.Element('test')
+
+class invalid_result:
+ @handlers.render_as('xml')
+ def GET(self):
+ return 'testing'
+
+class content_type:
+ @handlers.content_type('testing/contenttype')
+ def GET(self):
+ return 'whatever I want'
+
+handler_app = webtest.TestApp(
+ web.application(
+ ('/html', html,
+ '/xml', xml,
+ '/text', text,
+ '/xml_or_html', xml_or_html,
+ '/xml_list', xml_list,
+ '/invalid_type', invalid_type,
+ '/invalid_result', invalid_result,
+ '/content_type', content_type), locals()
+ ).wsgifunc()
+ )
+
+##################
+## Test classes ##
+##################
+
+class TestHandlersContentTypes(TestApi):
+
+ def test_sets_contenttype(self):
+ """ Response returns content-type based on Accept header """
+
+ for handler in handler_classes:
+ # responses will return default type always
+ return_type = handler.content_types[0]
+ for content_type in handler.content_types:
+
+ r = handler_app.get('/%s' % handler.short_name,
+ headers={'Accept': content_type})
+
+ assert r.headers['Content-Type'] == '%s; charset=%s' % \
+ (return_type,handler.encoding)
+
+
+ def test_contenttype_fallback(self):
+ """ If the Accept header is not supported, fallback to default type """
+
+ for handler in handler_classes:
+ # responses will return default type always
+ return_type = handler.content_types[0]
+ for content_type in handler.content_types:
+
+ r = handler_app.get('/%s' % handler.short_name,
+ headers={'Accept': 'superawesome/type'})
+
+ assert r.headers['Content-Type'] == '%s; charset=%s' % \
+ (return_type,handler.encoding)
+
+ def test_contenttype_no_accept(self):
+ """ Fallback to default type if there is not an Accept header. """
+
+ for handler in handler_classes:
+ # responses will return default type always
+ return_type = handler.content_types[0]
+ for content_type in handler.content_types:
+
+ r = handler_app.get('/%s' % handler.short_name,
+ headers={'Accept': ''})
+
+ assert r.headers['Content-Type'] == '%s; charset=%s' % \
+ (return_type,handler.encoding)
+
+ def test_multi_type_renders(self):
+ """ Test that methods decorated with multiple types issue format based on
+ the Accept header. """
+
+ html = handler_app.get('/xml_or_html', headers={'Accept':'text/html'})
+ xml = handler_app.get('/xml_or_html', headers={'Accept':'text/xml'})
+
+ xml_fallback = handler_app.get('/xml_or_html',
+ headers={'Accept':'text/json'})
+
+ assert html.headers['Content-Type'] == 'text/html; charset=utf-8'
+ assert xml.headers['Content-Type'] == 'application/xml; charset=utf-8'
+ assert xml_fallback.headers['Content-Type'] == \
+ 'application/xml; charset=utf-8'
+
+ def test_renders_list_results(self):
+ """ If a view returns a list of Elements, then the response should include
+ a serialization of all of those elements. """
+
+ r = handler_app.get('/xml_list')
+
+ assert r.headers['Content-Type'] == 'text/plain; charset=utf-8'
+ assert r.body == '<a/><w/><e/><s/><o/><m/><e/>'
+
+
+ def test_fails_on_invalid_type(self):
+ """ Using an invalid type with render_as results in an internal error """
+
+ try:
+ r = handler_app.get('/invalid_type')
+ except webtest.AppError:
+ assert True
+ else:
+ assert False
+
+ def test_fails_on_invalid_results(self):
+ """ Returning non-lxml results in an error when using render_as """
+
+ try:
+ r = handler_app.get('/invalid_result')
+ except webtest.AppError:
+ assert True
+ else:
+ assert False
+
+ def test_content_type(self):
+ """ content_type decorator successfully sets the Content-Type header """
+
+ r = handler_app.get('/content_type')
+ assert r.headers['Content-Type'] == 'testing/contenttype; charset=utf-8'
diff --git a/cc/api/tests/test_license.py b/cc/api/tests/test_license.py
index 0998c46..4b76a92 100644
--- a/cc/api/tests/test_license.py
+++ b/cc/api/tests/test_license.py
@@ -1,5 +1,7 @@
import os
+from urllib import urlencode
+import itertools
from cc.api.tests.test_common import *
@@ -82,12 +84,17 @@ class TestLicenseIssue(TestApi):
"""Issue with no answers or empty answers should return an error."""
res = self.app.post('/license/blarf/issue')
assert relax_validate(RELAX_ERROR, res.body)
+ res = self.app.post('/license/standard/issue')
+ assert relax_validate(RELAX_ERROR, res.body)
def test_invalid_answers(self):
"""Invalid answer string returns error."""
res = self.app.post('/license/standard/issue',
params={'answers':'<foo/>'})
assert relax_validate(RELAX_ERROR, res.body)
+ res = self.app.post('/license/standard/issue',
+ params={'answers':'<answers><locale>en</loca'})
+ assert relax_validate(RELAX_ERROR, res.body)
def _issue(self, lclass):
"""Common /issue testing code."""
@@ -137,14 +144,110 @@ class TestLicenseIssue(TestApi):
params={'answers':answers})
assert relax_validate(RELAX_ERROR, res.body)
+class TestLicenseWorkInfo(TestApi):
-class TestLicenseGet(TestApi):
+ _fields = {
+ 'title': 'Testing Title',
+ 'attribution_url': 'http://example.com/work-url',
+ 'source-url': 'http://example.com/source-url',
+ 'type': 'Text',
+ 'more_permissions_url': 'http://example.com/more-permissions-url',
+ }
+ _cc0_fields = {
+ 'title': 'Testing Title',
+ 'actor_href': 'http://example.com',
+ 'name': 'Testing',
+ 'work_jurisdiction': 'US',
+ }
+
+ _answers = '<answers><locale>en</locale><license-standard>' + \
+ '<commercial>y</commercial>' + \
+ '<derivatives>y</derivatives>' + \
+ '<jurisdiction></jurisdiction>' + \
+ '</license-standard>%s</answers>'
+
+ _cc0_answers = '<answers><license-zero />%s</answers>'
+
+ def _work_info_string(self, fields):
+ root = "<work-info>"
+ for k,v in fields.items():
+ root += "<%(key)s>%(value)s</%(key)s>" % {'key':k,'value':v}
+ root += "</work-info>"
+ return root
+
+ def _permute_workinfo(self, fields, values=None):
+ if values == None:
+ values = self._fields
+ work_infos = []
+ for i in range(1, len(fields) + 1):
+ for c in itertools.combinations(fields, i):
+ workfields = dict([ (f, values[f]) for f in c])
+ work_infos.append(workfields)
+ return work_infos
+
+ def test_license_workinfo(self):
+ """ get & issue should return identical (and valid) responses based on the
+ data provided in work-info. """
+
+ work_infos = self._permute_workinfo(self._fields.keys())
+
+ for workinfo in work_infos:
+ wi = self._work_info_string(
+ dict([ (f, self._fields[f]) for f in workinfo ]))
+ i = self.app.post('/license/standard/issue',
+ params={'answers':self._answers % wi})
+ g = self.app.get('/license/standard/get?' + urlencode(workinfo))
+
+ for f in workinfo:
+ # assert field is present in issue response
+ assert self._fields[f] in i.body, '%s=%s not in issue results.' % \
+ (f, self._fields[f])
+ # assert field is present in get response
+ assert self._fields[f] in g.body, '%s=%s not in get results.' % \
+ (f, self._fields[f])
+ # issue and get should be identical
+ assert i.body == g.body
+
+ work_infos = self._permute_workinfo(self._cc0_fields.keys(), self._cc0_fields)
+ for workinfo in work_infos:
+ wi = self._work_info_string(
+ dict([ (f, self._cc0_fields[f]) for f in workinfo ]))
+ i = self.app.post('/license/zero/issue',
+ params={'answers':self._cc0_answers % wi})
+ g = self.app.get('/license/zero/get?' + urlencode(workinfo))
+ for f in workinfo:
+ # assert field is present in issue response
+ assert self._cc0_fields[f] in i.body, '%s=%s not in issue results.'% \
+ (f, self._cc0_fields[f])
+ # assert field is present in get response
+ assert self._cc0_fields[f] in g.body, '%s=%s not in get results.' % \
+ (f, self._cc0_fields[f])
+ # issue and get should be identical
+ assert i.body == g.body
+ def test_invalid_work_jurisdiction(self):
+ """ cc.license will KeyError when an invalid work_jurisidction is passed to
+ the CC0 formatter. """
+ res = self.app.get('/license/zero/get?work_jurisdiction=PUKE')
+ assert relax_validate(RELAX_ERROR, res.body)
+ res = self.app.post('/license/zero/issue',
+ params={'answers': self._cc0_answers % (
+ '<work-info><work_jurisdiction>' + \
+ 'PUKE</work_jurisdiction></work-info>')})
+ assert relax_validate(RELAX_ERROR, res.body)
+
+class TestLicenseGet(TestApi):
+
def test_invalid_class(self):
"""/get should return an error with an invalid class."""
res = self.app.get('/license/%s/get' % hash(self))
assert relax_validate(RELAX_ERROR, res.body)
+ def test_invalid_answers(self):
+ """/get should return an error for invalid answers."""
+ res = self.app.get('/license/standard/get?commercial=z')
+ assert relax_validate(RELAX_ERROR, res.body)
+
def test_ignore_extra_args(self):
"""Test /get ignores extra nonsense arguments."""
for lclass in self.data.license_classes():
diff --git a/cc/api/tests/test_simple.py b/cc/api/tests/test_simple.py
index 6b31c3f..0362155 100644
--- a/cc/api/tests/test_simple.py
+++ b/cc/api/tests/test_simple.py
@@ -1,6 +1,6 @@
import os
-
+import lxml.html
from cc.api.tests.test_common import *
####################
@@ -24,12 +24,33 @@ class TestChooser(TestApi):
"""Test javascript wrapper over /simple/chooser."""
res = self.app.get('/simple/chooser')
jsres = self.app.get('/simple/chooser.js')
- opts = res.body.strip().split('\n')
+
+ opts = lxml.html.fromstring(res.body)
jsopts = jsres.body.strip().split('\n')
+
assert len(opts) == len(jsopts)
- for i in range(len(opts)):
- assert "document.write('%s');" % opts[i] == jsopts[i]
+
+ for i,opt in enumerate(opts):
+ assert "document.write('%s');" % lxml.html.tostring(opt) == jsopts[i]
+
+ # attempt with select tag...
+ res = self.app.get('/simple/chooser?select=testing')
+ jsres = self.app.get('/simple/chooser.js?select=testing')
+
+ opts = lxml.html.fromstring(res.body)
+ jsopts = jsres.body.strip().split('\n')
+
+ # <select> <options> </select>
+ assert (1 + len(opts) + 1) == len(jsopts)
+
+ assert jsopts[0] == "document.write('<select name=\"testing\">');"
+ assert opts.attrib['name'] == 'testing'
+
+ jsopts = jsopts[1:-1] # strip off select tags
+ for i,opt in enumerate(opts):
+ assert "document.write('%s');" % lxml.html.tostring(opt) == jsopts[i]
+
def test_ignore_extra_args(self):
"""Extra arguments are ignored."""
res = self.app.get('/simple/chooser?foo=bar')
diff --git a/cc/api/tests/test_support.py b/cc/api/tests/test_support.py
index 09b50aa..42a0244 100644
--- a/cc/api/tests/test_support.py
+++ b/cc/api/tests/test_support.py
@@ -1,5 +1,6 @@
import os
+import lxml.html
from cc.api.tests.test_common import *
@@ -24,11 +25,32 @@ class TestSupport(TestApi):
"""Test javascript wrapper over /support/jurisdictions."""
res = self.app.get('/support/jurisdictions')
jsres = self.app.get('/support/jurisdictions.js')
- opts = res.body.strip().split('\n')
+
+ opts = lxml.html.fromstring(res.body)
jsopts = jsres.body.strip().split('\n')
+
assert len(opts) == len(jsopts)
- for i in range(len(opts)):
- assert "document.write('%s');" % opts[i] == jsopts[i]
+
+ for i,opt in enumerate(opts):
+ assert "document.write('%s');" % lxml.html.tostring(opt) == jsopts[i]
+
+ # attempt with select tag...
+ res = self.app.get('/support/jurisdictions?select=testing')
+ jsres = self.app.get('/support/jurisdictions.js?select=testing')
+
+ opts = lxml.html.fromstring(res.body)
+ jsopts = jsres.body.strip().split('\n')
+
+ # <select> <options> </select>
+ assert (1 + len(opts) + 1) == len(jsopts)
+
+ assert jsopts[0] == "document.write('<select name=\"testing\">');"
+ assert opts.attrib['name'] == 'testing'
+
+ jsopts = jsopts[1:-1] # strip off select tags
+ for i,opt in enumerate(opts):
+ assert "document.write('%s');" % lxml.html.tostring(opt) == jsopts[i]
+
def test_ignore_extra_args(self):
"""Extra arguments are ignored."""
diff --git a/setup.py b/setup.py
index 0715446..18c89b0 100644
--- a/setup.py
+++ b/setup.py
@@ -28,7 +28,8 @@ requires = [
'lxml',
'web.py',
'nose',
- 'cc.license',
+ 'cc.i18npkg==0.2',
+ 'cc.license==0.11.10',
'WebTest',
]
hooks/post-receive
--
cc.api based on sane cc.license
More information about the cc-commits
mailing list