aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorIan Cordasco <graffatcolmingov@gmail.com>2015-10-04 13:47:55 -0500
committerIan Cordasco <graffatcolmingov@gmail.com>2015-10-04 13:48:41 -0500
commit2ecd303d0a52895f263cd775a3113b4467062b10 (patch)
tree3a5ce01beeb3338893ef1b7e7972dd9bbbfd9d47 /tests
parent224c5f7db47ab55b30632d60ea84e7a69b8a95bd (diff)
downloaduritemplates-2ecd303d0a52895f263cd775a3113b4467062b10.tar.gz
Clean up and actually run tests from fixtures
Diffstat (limited to 'tests')
-rw-r--r--tests/fixtures/README.md91
-rw-r--r--tests/fixtures/extended-tests.json118
-rw-r--r--tests/fixtures/json2xml.xslt201
-rw-r--r--tests/fixtures/negative-tests.json57
-rw-r--r--tests/fixtures/spec-examples-by-section.json439
-rw-r--r--tests/fixtures/spec-examples.json218
-rw-r--r--tests/fixtures/transform-json-tests.xslt51
-rw-r--r--tests/test_from_fixtures.py119
-rw-r--r--tests/test_uritemplate.py585
9 files changed, 1879 insertions, 0 deletions
diff --git a/tests/fixtures/README.md b/tests/fixtures/README.md
new file mode 100644
index 0000000..11dcddb
--- /dev/null
+++ b/tests/fixtures/README.md
@@ -0,0 +1,91 @@
+Theses test are borrowed from https://github.com/uri-templates/uritemplate-test
+at commit: fdd5d611a849b922c2ff40fc3997fd265dd14c02
+URI Template Tests
+==================
+
+This is a set of tests for implementations of
+[RFC6570](http://tools.ietf.org/html/rfc6570) - URI Template. It is designed
+to be reused by any implementation, to improve interoperability and
+implementation quality.
+
+If your project uses Git for version control, you can make uritemplate-tests into a [submodule](http://help.github.com/submodules/).
+
+Test Format
+-----------
+
+Each test file is a [JSON](http://tools.ietf.org/html/RFC6627) document
+containing an object whose properties are groups of related tests.
+Alternatively, all tests are available in XML as well, with the XML files
+being generated by transform-json-tests.xslt which uses json2xml.xslt as a
+general-purpose JSON-to-XML parsing library.
+
+Each group, in turn, is an object with three children:
+
+* level - the level of the tests covered, as per the RFC (optional; if absent,
+ assume level 4).
+* variables - an object representing the variables that are available to the
+ tests in the suite
+* testcases - a list of testcases, where each case is a two-member list, the
+ first being the template, the second being the result of expanding the
+ template with the provided variables.
+
+Note that the result string can be a few different things:
+
+* string - if the second member is a string, the result of expansion is
+ expected to match it, character-for-character.
+* list - if the second member is a list of strings, the result of expansion
+ is expected to match one of them; this allows for templates that can
+ expand into different, equally-acceptable URIs.
+* false - if the second member is boolean false, expansion is expected to
+ fail (i.e., the template was invalid).
+
+For example:
+
+ {
+ "Level 1 Examples" :
+ {
+ "level": 1,
+ "variables": {
+ "var" : "value",
+ "hello" : "Hello World!"
+ },
+ "testcases" : [
+ ["{var}", "value"],
+ ["{hello}", "Hello%20World%21"]
+ ]
+ }
+ }
+
+
+Tests Included
+--------------
+
+The following test files are included:
+
+* spec-examples.json - The complete set of example templates from the RFC
+* spec-examples-by-section.json - The examples, section by section
+* extended-tests.json - more complex test cases
+* negative-tests.json - invalid templates
+
+For all these test files, XML versions with the names *.xml can be
+generated with the transform-json-tests.xslt XSLT stylesheet. The XSLT
+contains the names of the above test files as a parameter, and can be
+started with any XML as input (i.e., the XML input is ignored).
+
+License
+-------
+
+ Copyright 2011-2012 The Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
diff --git a/tests/fixtures/extended-tests.json b/tests/fixtures/extended-tests.json
new file mode 100644
index 0000000..fd69744
--- /dev/null
+++ b/tests/fixtures/extended-tests.json
@@ -0,0 +1,118 @@
+{
+ "Additional Examples 1":{
+ "level":4,
+ "variables":{
+ "id" : "person",
+ "token" : "12345",
+ "fields" : ["id", "name", "picture"],
+ "format" : "json",
+ "q" : "URI Templates",
+ "page" : "5",
+ "lang" : "en",
+ "geocode" : ["37.76","-122.427"],
+ "first_name" : "John",
+ "last.name" : "Doe",
+ "Some%20Thing" : "foo",
+ "number" : 6,
+ "long" : 37.76,
+ "lat" : -122.427,
+ "group_id" : "12345",
+ "query" : "PREFIX dc: <http://purl.org/dc/elements/1.1/> SELECT ?book ?who WHERE { ?book dc:creator ?who }",
+ "uri" : "http://example.org/?uri=http%3A%2F%2Fexample.org%2F",
+ "word" : "drücken",
+ "Stra%C3%9Fe" : "Grüner Weg",
+ "random" : "šö䟜ñꀣ¥‡ÑÒÓÔÕÖ×ØÙÚàáâãäåæçÿ",
+ "assoc_special_chars" :
+ { "šö䟜ñꀣ¥‡ÑÒÓÔÕ" : "Ö×ØÙÚàáâãäåæçÿ" }
+ },
+ "testcases":[
+
+ [ "{/id*}" , "/person" ],
+ [ "{/id*}{?fields,first_name,last.name,token}" , [
+ "/person?fields=id,name,picture&first_name=John&last.name=Doe&token=12345",
+ "/person?fields=id,picture,name&first_name=John&last.name=Doe&token=12345",
+ "/person?fields=picture,name,id&first_name=John&last.name=Doe&token=12345",
+ "/person?fields=picture,id,name&first_name=John&last.name=Doe&token=12345",
+ "/person?fields=name,picture,id&first_name=John&last.name=Doe&token=12345",
+ "/person?fields=name,id,picture&first_name=John&last.name=Doe&token=12345"]
+ ],
+ ["/search.{format}{?q,geocode,lang,locale,page,result_type}",
+ [ "/search.json?q=URI%20Templates&geocode=37.76,-122.427&lang=en&page=5",
+ "/search.json?q=URI%20Templates&geocode=-122.427,37.76&lang=en&page=5"]
+ ],
+ ["/test{/Some%20Thing}", "/test/foo" ],
+ ["/set{?number}", "/set?number=6"],
+ ["/loc{?long,lat}" , "/loc?long=37.76&lat=-122.427"],
+ ["/base{/group_id,first_name}/pages{/page,lang}{?format,q}","/base/12345/John/pages/5/en?format=json&q=URI%20Templates"],
+ ["/sparql{?query}", "/sparql?query=PREFIX%20dc%3A%20%3Chttp%3A%2F%2Fpurl.org%2Fdc%2Felements%2F1.1%2F%3E%20SELECT%20%3Fbook%20%3Fwho%20WHERE%20%7B%20%3Fbook%20dc%3Acreator%20%3Fwho%20%7D"],
+ ["/go{?uri}", "/go?uri=http%3A%2F%2Fexample.org%2F%3Furi%3Dhttp%253A%252F%252Fexample.org%252F"],
+ ["/service{?word}", "/service?word=dr%C3%BCcken"],
+ ["/lookup{?Stra%C3%9Fe}", "/lookup?Stra%C3%9Fe=Gr%C3%BCner%20Weg"],
+ ["{random}" , "%C5%A1%C3%B6%C3%A4%C5%B8%C5%93%C3%B1%C3%AA%E2%82%AC%C2%A3%C2%A5%E2%80%A1%C3%91%C3%92%C3%93%C3%94%C3%95%C3%96%C3%97%C3%98%C3%99%C3%9A%C3%A0%C3%A1%C3%A2%C3%A3%C3%A4%C3%A5%C3%A6%C3%A7%C3%BF"],
+ ["{?assoc_special_chars*}", "?%C5%A1%C3%B6%C3%A4%C5%B8%C5%93%C3%B1%C3%AA%E2%82%AC%C2%A3%C2%A5%E2%80%A1%C3%91%C3%92%C3%93%C3%94%C3%95=%C3%96%C3%97%C3%98%C3%99%C3%9A%C3%A0%C3%A1%C3%A2%C3%A3%C3%A4%C3%A5%C3%A6%C3%A7%C3%BF"]
+ ]
+ },
+ "Additional Examples 2":{
+ "level":4,
+ "variables":{
+ "id" : ["person","albums"],
+ "token" : "12345",
+ "fields" : ["id", "name", "picture"],
+ "format" : "atom",
+ "q" : "URI Templates",
+ "page" : "10",
+ "start" : "5",
+ "lang" : "en",
+ "geocode" : ["37.76","-122.427"]
+ },
+ "testcases":[
+
+ [ "{/id*}" , ["/person/albums","/albums/person"] ],
+ [ "{/id*}{?fields,token}" , [
+ "/person/albums?fields=id,name,picture&token=12345",
+ "/person/albums?fields=id,picture,name&token=12345",
+ "/person/albums?fields=picture,name,id&token=12345",
+ "/person/albums?fields=picture,id,name&token=12345",
+ "/person/albums?fields=name,picture,id&token=12345",
+ "/person/albums?fields=name,id,picture&token=12345",
+ "/albums/person?fields=id,name,picture&token=12345",
+ "/albums/person?fields=id,picture,name&token=12345",
+ "/albums/person?fields=picture,name,id&token=12345",
+ "/albums/person?fields=picture,id,name&token=12345",
+ "/albums/person?fields=name,picture,id&token=12345",
+ "/albums/person?fields=name,id,picture&token=12345"]
+ ]
+ ]
+ },
+ "Additional Examples 3: Empty Variables":{
+ "variables" : {
+ "empty_list" : [],
+ "empty_assoc" : {}
+ },
+ "testcases":[
+ [ "{/empty_list}", [ "" ] ],
+ [ "{/empty_list*}", [ "" ] ],
+ [ "{?empty_list}", [ ""] ],
+ [ "{?empty_list*}", [ "" ] ],
+ [ "{?empty_assoc}", [ "" ] ],
+ [ "{?empty_assoc*}", [ "" ] ]
+ ]
+ },
+ "Additional Examples 4: Numeric Keys":{
+ "variables" : {
+ "42" : "The Answer to the Ultimate Question of Life, the Universe, and Everything",
+ "1337" : ["leet", "as","it", "can","be"],
+ "german" : {
+ "11": "elf",
+ "12": "zwölf"
+ }
+ },
+ "testcases":[
+ [ "{42}", "The%20Answer%20to%20the%20Ultimate%20Question%20of%20Life%2C%20the%20Universe%2C%20and%20Everything"],
+ [ "{?42}", "?42=The%20Answer%20to%20the%20Ultimate%20Question%20of%20Life%2C%20the%20Universe%2C%20and%20Everything"],
+ [ "{1337}", "leet,as,it,can,be"],
+ [ "{?1337*}", "?1337=leet&1337=as&1337=it&1337=can&1337=be"],
+ [ "{?german*}", [ "?11=elf&12=zw%C3%B6lf", "?12=zw%C3%B6lf&11=elf"] ]
+ ]
+ }
+}
diff --git a/tests/fixtures/json2xml.xslt b/tests/fixtures/json2xml.xslt
new file mode 100644
index 0000000..59b3548
--- /dev/null
+++ b/tests/fixtures/json2xml.xslt
@@ -0,0 +1,201 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Downloaded on 12/6/2012 from http://www.gerixsoft.com/blog/xslt/json2xml -->
+
+<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+ <xsl:template match="json">
+ <xsl:copy>
+ <xsl:copy-of select="@*"/>
+ <xsl:call-template name="json2xml">
+ <xsl:with-param name="text" select="."/>
+ </xsl:call-template>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template name="json2xml">
+ <xsl:param name="text"/>
+ <xsl:variable name="mode0">
+ <xsl:variable name="regexps" select="'//(.*?)\n', '/\*(.*?)\*/', '(''|&quot;)(([^\\]|\\[\\&quot;''/btnvfr])*?)\3', '(-?\d+(\.\d+([eE][+-]?\d+)?|[eE][+-]?\d+))', '(-?[1-9]\d*)', '(-?0[0-7]+)', '(-?0x[0-9a-fA-F]+)', '([:,\{\}\[\]])', '(true|false)', '(null)'"/>
+ <xsl:analyze-string select="$text" regex="{string-join($regexps,'|')}" flags="s">
+ <xsl:matching-substring>
+ <xsl:choose>
+ <!-- single line comment -->
+ <xsl:when test="regex-group(1)">
+ <xsl:comment>
+ <xsl:value-of select="regex-group(1)"/>
+ </xsl:comment>
+ <xsl:text>&#10;</xsl:text>
+ </xsl:when>
+ <!-- multi line comment -->
+ <xsl:when test="regex-group(2)">
+ <xsl:comment>
+ <xsl:value-of select="regex-group(2)"/>
+ </xsl:comment>
+ </xsl:when>
+ <!-- string -->
+ <xsl:when test="regex-group(3)">
+ <string>
+ <xsl:analyze-string select="regex-group(4)" regex="\\([\\&quot;'/btnvfr])" flags="s">
+ <xsl:matching-substring>
+ <xsl:variable name="s" select="regex-group(1)"/>
+ <xsl:choose>
+ <xsl:when test="$s=('\', '&quot;', '''', '/')">
+ <xsl:value-of select="regex-group(1)"/>
+ </xsl:when>
+ <xsl:when test="$s='b'">
+ <!--xsl:text>&#8;</xsl:text-->
+ <xsl:message select="'escape sequense \b is not supported by XML'"/>
+ <xsl:text>\b</xsl:text>
+ </xsl:when>
+ <xsl:when test="$s='t'">
+ <xsl:text>&#9;</xsl:text>
+ </xsl:when>
+ <xsl:when test="$s='n'">
+ <xsl:text>&#10;</xsl:text>
+ </xsl:when>
+ <xsl:when test="$s='v'">
+ <!--xsl:text>&#11;</xsl:text-->
+ <xsl:message select="'escape sequence \v is not supported by XML'"/>
+ <xsl:text>\v</xsl:text>
+ </xsl:when>
+ <xsl:when test="$s='f'">
+ <!--xsl:text>&#12;</xsl:text-->
+ <xsl:message select="'escape sequence \f is not supported by XML'"/>
+ <xsl:text>\f</xsl:text>
+ </xsl:when>
+ <xsl:when test="$s='r'">
+ <xsl:text>&#13;</xsl:text>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes" select="'internal error'"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:matching-substring>
+ <xsl:non-matching-substring>
+ <xsl:value-of select="."/>
+ </xsl:non-matching-substring>
+ </xsl:analyze-string>
+ </string>
+ </xsl:when>
+ <!-- double -->
+ <xsl:when test="regex-group(6)">
+ <double>
+ <xsl:value-of select="regex-group(6)"/>
+ </double>
+ </xsl:when>
+ <!-- integer -->
+ <xsl:when test="regex-group(9)">
+ <integer>
+ <xsl:value-of select="regex-group(9)"/>
+ </integer>
+ </xsl:when>
+ <!-- octal -->
+ <xsl:when test="regex-group(10)">
+ <integer>
+ <xsl:value-of xmlns:Integer="java:java.lang.Integer" select="Integer:parseInt(regex-group(10), 8)"/>
+ </integer>
+ </xsl:when>
+ <!-- hex -->
+ <xsl:when test="regex-group(11)">
+ <integer>
+ <xsl:value-of xmlns:Integer="java:java.lang.Integer" select="Integer:parseInt(replace(regex-group(11), '0x', ''), 16)"/>
+ </integer>
+ </xsl:when>
+ <!-- symbol -->
+ <xsl:when test="regex-group(12)">
+ <symbol>
+ <xsl:value-of select="regex-group(12)"/>
+ </symbol>
+ </xsl:when>
+ <!-- boolean -->
+ <xsl:when test="regex-group(13)">
+ <boolean>
+ <xsl:value-of select="regex-group(13)"/>
+ </boolean>
+ </xsl:when>
+ <!-- null -->
+ <xsl:when test="regex-group(14)">
+ <null />
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:message terminate="yes" select="'internal error'"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:matching-substring>
+ <xsl:non-matching-substring>
+ <xsl:if test="normalize-space()!=''">
+ <xsl:message select="concat('unknown token: ', .)"/>
+ <xsl:value-of select="."/>
+ </xsl:if>
+ </xsl:non-matching-substring>
+ </xsl:analyze-string>
+ </xsl:variable>
+ <xsl:variable name="mode1">
+ <xsl:apply-templates mode="json2xml1" select="$mode0/node()[1]"/>
+ </xsl:variable>
+ <xsl:variable name="mode2">
+ <xsl:apply-templates mode="json2xml2" select="$mode1"/>
+ </xsl:variable>
+ <xsl:variable name="mode3">
+ <xsl:apply-templates mode="json2xml3" select="$mode2"/>
+ </xsl:variable>
+ <xsl:copy-of select="$mode3"/> <!-- change $mode3 to $mode[0-2] for easy debug -->
+ </xsl:template>
+
+ <!-- json2xml1 mode: group content between {} and [] into object and array elements -->
+
+ <xsl:template mode="json2xml1" match="node()" priority="-9">
+ <xsl:copy-of select="."/>
+ <xsl:apply-templates mode="json2xml1" select="following-sibling::node()[1]"/>
+ </xsl:template>
+
+ <xsl:template mode="json2xml1" match="symbol[.=('}',']')]"/>
+
+ <xsl:template mode="json2xml1" match="symbol[.=('{','[')]">
+ <xsl:element name="{if (.='{') then 'object' else 'array'}">
+ <xsl:apply-templates mode="json2xml1" select="following-sibling::node()[1]"/>
+ </xsl:element>
+ <xsl:variable name="level" select="count(preceding-sibling::symbol[.=('{','[')])-count(preceding-sibling::symbol[.=('}',']')])+1"/>
+ <xsl:variable name="ender"
+ select="following-sibling::symbol[.=('}',']') and count(preceding-sibling::symbol[.=('{','[')])-count(preceding-sibling::symbol[.=('}',']')])=$level][1]"/>
+ <xsl:apply-templates mode="json2xml1" select="$ender/following-sibling::node()[1]"/>
+ </xsl:template>
+
+ <!-- json2xml2 mode: group <string>:<string|integer|double|object|array> into field element -->
+
+ <xsl:template priority="-9" mode="json2xml2" match="@*|node()">
+ <xsl:copy>
+ <xsl:apply-templates mode="json2xml2" select="@*|node()"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template mode="json2xml2"
+ match="string[following-sibling::*[1]/self::symbol[.=':'] and following-sibling::*[2]/(self::string|self::integer|self::double|self::boolean|self::object|self::array|self::null)]"/>
+
+ <xsl:template mode="json2xml2"
+ match="symbol[.=':'][preceding-sibling::*[1]/self::string and following-sibling::*[1]/(self::string|self::integer|self::double|self::boolean|self::object|self::array|self::null)]">
+ <field name="{preceding-sibling::*[1]}">
+ <xsl:for-each select="following-sibling::*[1]">
+ <xsl:copy>
+ <xsl:apply-templates mode="json2xml2" select="@*|node()"/>
+ </xsl:copy>
+ </xsl:for-each>
+ </field>
+ </xsl:template>
+
+ <xsl:template mode="json2xml2"
+ match="*[self::string|self::integer|self::double|self::boolean|self::object|self::array|self::null][preceding-sibling::*[2]/self::string and preceding-sibling::*[1]/self::symbol[.=':']]"/>
+
+ <!-- json2xml3 mode: drop comma between consecutive field and object elements -->
+
+ <xsl:template priority="-9" mode="json2xml3" match="@*|node()">
+ <xsl:copy>
+ <xsl:apply-templates mode="json2xml3" select="@*|node()"/>
+ </xsl:copy>
+ </xsl:template>
+
+ <xsl:template mode="json2xml3" match="object/symbol[.=','][preceding-sibling::*[1]/self::field and following-sibling::*[1]/self::field]"/>
+
+ <xsl:template mode="json2xml3" match="array/symbol[.=','][preceding-sibling::*[1]/(self::string|self::integer|self::double|self::boolean|self::object|self::array|self::null) and following-sibling::*[1]/(self::string|self::integer|self::double|self::boolean|self::object|self::array|self::null)]"/>
+
+</xsl:stylesheet> \ No newline at end of file
diff --git a/tests/fixtures/negative-tests.json b/tests/fixtures/negative-tests.json
new file mode 100644
index 0000000..552a6bf
--- /dev/null
+++ b/tests/fixtures/negative-tests.json
@@ -0,0 +1,57 @@
+{
+ "Failure Tests":{
+ "level":4,
+ "variables":{
+ "id" : "thing",
+ "var" : "value",
+ "hello" : "Hello World!",
+ "with space" : "fail",
+ " leading_space" : "Hi!",
+ "trailing_space " : "Bye!",
+ "empty" : "",
+ "path" : "/foo/bar",
+ "x" : "1024",
+ "y" : "768",
+ "list" : ["red", "green", "blue"],
+ "keys" : { "semi" : ";", "dot" : ".", "comma" : ","},
+ "example" : "red",
+ "searchTerms" : "uri templates",
+ "~thing" : "some-user",
+ "default-graph-uri" : ["http://www.example/book/","http://www.example/papers/"],
+ "query" : "PREFIX dc: <http://purl.org/dc/elements/1.1/> SELECT ?book ?who WHERE { ?book dc:creator ?who }"
+
+ },
+ "testcases":[
+ [ "{/id*", false ],
+ [ "/id*}", false ],
+ [ "{/?id}", false ],
+ [ "{var:prefix}", false ],
+ [ "{hello:2*}", false ] ,
+ [ "{??hello}", false ] ,
+ [ "{!hello}", false ] ,
+ [ "{with space}", false],
+ [ "{ leading_space}", false],
+ [ "{trailing_space }", false],
+ [ "{=path}", false ] ,
+ [ "{$var}", false ],
+ [ "{|var*}", false ],
+ [ "{*keys?}", false ],
+ [ "{?empty=default,var}", false ],
+ [ "{var}{-prefix|/-/|var}" , false ],
+ [ "?q={searchTerms}&amp;c={example:color?}" , false ],
+ [ "x{?empty|foo=none}" , false ],
+ [ "/h{#hello+}" , false ],
+ [ "/h#{hello+}" , false ],
+ [ "{keys:1}", false ],
+ [ "{+keys:1}", false ],
+ [ "{;keys:1*}", false ],
+ [ "?{-join|&|var,list}" , false ],
+ [ "/people/{~thing}", false],
+ [ "/{default-graph-uri}", false ],
+ [ "/sparql{?query,default-graph-uri}", false ],
+ [ "/sparql{?query){&default-graph-uri*}", false ],
+ [ "/resolution{?x, y}" , false ]
+
+ ]
+ }
+} \ No newline at end of file
diff --git a/tests/fixtures/spec-examples-by-section.json b/tests/fixtures/spec-examples-by-section.json
new file mode 100644
index 0000000..5aef182
--- /dev/null
+++ b/tests/fixtures/spec-examples-by-section.json
@@ -0,0 +1,439 @@
+{
+ "3.2.1 Variable Expansion" :
+ {
+ "variables": {
+ "count" : ["one", "two", "three"],
+ "dom" : ["example", "com"],
+ "dub" : "me/too",
+ "hello" : "Hello World!",
+ "half" : "50%",
+ "var" : "value",
+ "who" : "fred",
+ "base" : "http://example.com/home/",
+ "path" : "/foo/bar",
+ "list" : ["red", "green", "blue"],
+ "keys" : { "semi" : ";", "dot" : ".", "comma" : ","},
+ "v" : "6",
+ "x" : "1024",
+ "y" : "768",
+ "empty" : "",
+ "empty_keys" : [],
+ "undef" : null
+ },
+ "testcases" : [
+ ["{count}", "one,two,three"],
+ ["{count*}", "one,two,three"],
+ ["{/count}", "/one,two,three"],
+ ["{/count*}", "/one/two/three"],
+ ["{;count}", ";count=one,two,three"],
+ ["{;count*}", ";count=one;count=two;count=three"],
+ ["{?count}", "?count=one,two,three"],
+ ["{?count*}", "?count=one&count=two&count=three"],
+ ["{&count*}", "&count=one&count=two&count=three"]
+ ]
+ },
+ "3.2.2 Simple String Expansion" :
+ {
+ "variables": {
+ "count" : ["one", "two", "three"],
+ "dom" : ["example", "com"],
+ "dub" : "me/too",
+ "hello" : "Hello World!",
+ "half" : "50%",
+ "var" : "value",
+ "who" : "fred",
+ "base" : "http://example.com/home/",
+ "path" : "/foo/bar",
+ "list" : ["red", "green", "blue"],
+ "keys" : { "semi" : ";", "dot" : ".", "comma" : ","},
+ "v" : "6",
+ "x" : "1024",
+ "y" : "768",
+ "empty" : "",
+ "empty_keys" : [],
+ "undef" : null
+ },
+ "testcases" : [
+ ["{var}", "value"],
+ ["{hello}", "Hello%20World%21"],
+ ["{half}", "50%25"],
+ ["O{empty}X", "OX"],
+ ["O{undef}X", "OX"],
+ ["{x,y}", "1024,768"],
+ ["{x,hello,y}", "1024,Hello%20World%21,768"],
+ ["?{x,empty}", "?1024,"],
+ ["?{x,undef}", "?1024"],
+ ["?{undef,y}", "?768"],
+ ["{var:3}", "val"],
+ ["{var:30}", "value"],
+ ["{list}", "red,green,blue"],
+ ["{list*}", "red,green,blue"],
+ ["{keys}", [
+ "comma,%2C,dot,.,semi,%3B",
+ "comma,%2C,semi,%3B,dot,.",
+ "dot,.,comma,%2C,semi,%3B",
+ "dot,.,semi,%3B,comma,%2C",
+ "semi,%3B,comma,%2C,dot,.",
+ "semi,%3B,dot,.,comma,%2C"
+ ]],
+ ["{keys*}", [
+ "comma=%2C,dot=.,semi=%3B",
+ "comma=%2C,semi=%3B,dot=.",
+ "dot=.,comma=%2C,semi=%3B",
+ "dot=.,semi=%3B,comma=%2C",
+ "semi=%3B,comma=%2C,dot=.",
+ "semi=%3B,dot=.,comma=%2C"
+ ]]
+ ]
+ },
+ "3.2.3 Reserved Expansion" :
+ {
+ "variables": {
+ "count" : ["one", "two", "three"],
+ "dom" : ["example", "com"],
+ "dub" : "me/too",
+ "hello" : "Hello World!",
+ "half" : "50%",
+ "var" : "value",
+ "who" : "fred",
+ "base" : "http://example.com/home/",
+ "path" : "/foo/bar",
+ "list" : ["red", "green", "blue"],
+ "keys" : { "semi" : ";", "dot" : ".", "comma" : ","},
+ "v" : "6",
+ "x" : "1024",
+ "y" : "768",
+ "empty" : "",
+ "empty_keys" : [],
+ "undef" : null
+ },
+ "testcases" : [
+ ["{+var}", "value"],
+ ["{/var,empty}", "/value/"],
+ ["{/var,undef}", "/value"],
+ ["{+hello}", "Hello%20World!"],
+ ["{+half}", "50%25"],
+ ["{base}index", "http%3A%2F%2Fexample.com%2Fhome%2Findex"],
+ ["{+base}index", "http://example.com/home/index"],
+ ["O{+empty}X", "OX"],
+ ["O{+undef}X", "OX"],
+ ["{+path}/here", "/foo/bar/here"],
+ ["{+path:6}/here", "/foo/b/here"],
+ ["here?ref={+path}", "here?ref=/foo/bar"],
+ ["up{+path}{var}/here", "up/foo/barvalue/here"],
+ ["{+x,hello,y}", "1024,Hello%20World!,768"],
+ ["{+path,x}/here", "/foo/bar,1024/here"],
+ ["{+list}", "red,green,blue"],
+ ["{+list*}", "red,green,blue"],
+ ["{+keys}", [
+ "comma,,,dot,.,semi,;",
+ "comma,,,semi,;,dot,.",
+ "dot,.,comma,,,semi,;",
+ "dot,.,semi,;,comma,,",
+ "semi,;,comma,,,dot,.",
+ "semi,;,dot,.,comma,,"
+ ]],
+ ["{+keys*}", [
+ "comma=,,dot=.,semi=;",
+ "comma=,,semi=;,dot=.",
+ "dot=.,comma=,,semi=;",
+ "dot=.,semi=;,comma=,",
+ "semi=;,comma=,,dot=.",
+ "semi=;,dot=.,comma=,"
+ ]]
+ ]
+ },
+ "3.2.4 Fragment Expansion" :
+ {
+ "variables": {
+ "count" : ["one", "two", "three"],
+ "dom" : ["example", "com"],
+ "dub" : "me/too",
+ "hello" : "Hello World!",
+ "half" : "50%",
+ "var" : "value",
+ "who" : "fred",
+ "base" : "http://example.com/home/",
+ "path" : "/foo/bar",
+ "list" : ["red", "green", "blue"],
+ "keys" : { "semi" : ";", "dot" : ".", "comma" : ","},
+ "v" : "6",
+ "x" : "1024",
+ "y" : "768",
+ "empty" : "",
+ "empty_keys" : [],
+ "undef" : null
+ },
+ "testcases" : [
+ ["{#var}", "#value"],
+ ["{#hello}", "#Hello%20World!"],
+ ["{#half}", "#50%25"],
+ ["foo{#empty}", "foo#"],
+ ["foo{#undef}", "foo"],
+ ["{#x,hello,y}", "#1024,Hello%20World!,768"],
+ ["{#path,x}/here", "#/foo/bar,1024/here"],
+ ["{#path:6}/here", "#/foo/b/here"],
+ ["{#list}", "#red,green,blue"],
+ ["{#list*}", "#red,green,blue"],
+ ["{#keys}", [
+ "#comma,,,dot,.,semi,;",
+ "#comma,,,semi,;,dot,.",
+ "#dot,.,comma,,,semi,;",
+ "#dot,.,semi,;,comma,,",
+ "#semi,;,comma,,,dot,.",
+ "#semi,;,dot,.,comma,,"
+ ]]
+ ]
+ },
+ "3.2.5 Label Expansion with Dot-Prefix" :
+ {
+ "variables": {
+ "count" : ["one", "two", "three"],
+ "dom" : ["example", "com"],
+ "dub" : "me/too",
+ "hello" : "Hello World!",
+ "half" : "50%",
+ "var" : "value",
+ "who" : "fred",
+ "base" : "http://example.com/home/",
+ "path" : "/foo/bar",
+ "list" : ["red", "green", "blue"],
+ "keys" : { "semi" : ";", "dot" : ".", "comma" : ","},
+ "v" : "6",
+ "x" : "1024",
+ "y" : "768",
+ "empty" : "",
+ "empty_keys" : [],
+ "undef" : null
+ },
+ "testcases" : [
+ ["{.who}", ".fred"],
+ ["{.who,who}", ".fred.fred"],
+ ["{.half,who}", ".50%25.fred"],
+ ["www{.dom*}", "www.example.com"],
+ ["X{.var}", "X.value"],
+ ["X{.var:3}", "X.val"],
+ ["X{.empty}", "X."],
+ ["X{.undef}", "X"],
+ ["X{.list}", "X.red,green,blue"],
+ ["X{.list*}", "X.red.green.blue"],
+ ["{#keys}", [
+ "#comma,,,dot,.,semi,;",
+ "#comma,,,semi,;,dot,.",
+ "#dot,.,comma,,,semi,;",
+ "#dot,.,semi,;,comma,,",
+ "#semi,;,comma,,,dot,.",
+ "#semi,;,dot,.,comma,,"
+ ]],
+ ["{#keys*}", [
+ "#comma=,,dot=.,semi=;",
+ "#comma=,,semi=;,dot=.",
+ "#dot=.,comma=,,semi=;",
+ "#dot=.,semi=;,comma=,",
+ "#semi=;,comma=,,dot=.",
+ "#semi=;,dot=.,comma=,"
+ ]],
+ ["X{.empty_keys}", "X"],
+ ["X{.empty_keys*}", "X"]
+ ]
+ },
+ "3.2.6 Path Segment Expansion" :
+ {
+ "variables": {
+ "count" : ["one", "two", "three"],
+ "dom" : ["example", "com"],
+ "dub" : "me/too",
+ "hello" : "Hello World!",
+ "half" : "50%",
+ "var" : "value",
+ "who" : "fred",
+ "base" : "http://example.com/home/",
+ "path" : "/foo/bar",
+ "list" : ["red", "green", "blue"],
+ "keys" : { "semi" : ";", "dot" : ".", "comma" : ","},
+ "v" : "6",
+ "x" : "1024",
+ "y" : "768",
+ "empty" : "",
+ "empty_keys" : [],
+ "undef" : null
+ },
+ "testcases" : [
+ ["{/who}", "/fred"],
+ ["{/who,who}", "/fred/fred"],
+ ["{/half,who}", "/50%25/fred"],
+ ["{/who,dub}", "/fred/me%2Ftoo"],
+ ["{/var}", "/value"],
+ ["{/var,empty}", "/value/"],
+ ["{/var,undef}", "/value"],
+ ["{/var,x}/here", "/value/1024/here"],
+ ["{/var:1,var}", "/v/value"],
+ ["{/list}", "/red,green,blue"],
+ ["{/list*}", "/red/green/blue"],
+ ["{/list*,path:4}", "/red/green/blue/%2Ffoo"],
+ ["{/keys}", [
+ "/comma,%2C,dot,.,semi,%3B",
+ "/comma,%2C,semi,%3B,dot,.",
+ "/dot,.,comma,%2C,semi,%3B",
+ "/dot,.,semi,%3B,comma,%2C",
+ "/semi,%3B,comma,%2C,dot,.",
+ "/semi,%3B,dot,.,comma,%2C"
+ ]],
+ ["{/keys*}", [
+ "/comma=%2C/dot=./semi=%3B",
+ "/comma=%2C/semi=%3B/dot=.",
+ "/dot=./comma=%2C/semi=%3B",
+ "/dot=./semi=%3B/comma=%2C",
+ "/semi=%3B/comma=%2C/dot=.",
+ "/semi=%3B/dot=./comma=%2C"
+ ]]
+ ]
+ },
+ "3.2.7 Path-Style Parameter Expansion" :
+ {
+ "variables": {
+ "count" : ["one", "two", "three"],
+ "dom" : ["example", "com"],
+ "dub" : "me/too",
+ "hello" : "Hello World!",
+ "half" : "50%",
+ "var" : "value",
+ "who" : "fred",
+ "base" : "http://example.com/home/",
+ "path" : "/foo/bar",
+ "list" : ["red", "green", "blue"],
+ "keys" : { "semi" : ";", "dot" : ".", "comma" : ","},
+ "v" : "6",
+ "x" : "1024",
+ "y" : "768",
+ "empty" : "",
+ "empty_keys" : [],
+ "undef" : null
+ },
+ "testcases" : [
+ ["{;who}", ";who=fred"],
+ ["{;half}", ";half=50%25"],
+ ["{;empty}", ";empty"],
+ ["{;hello:5}", ";hello=Hello"],
+ ["{;v,empty,who}", ";v=6;empty;who=fred"],
+ ["{;v,bar,who}", ";v=6;who=fred"],
+ ["{;x,y}", ";x=1024;y=768"],
+ ["{;x,y,empty}", ";x=1024;y=768;empty"],
+ ["{;x,y,undef}", ";x=1024;y=768"],
+ ["{;list}", ";list=red,green,blue"],
+ ["{;list*}", ";list=red;list=green;list=blue"],
+ ["{;keys}", [
+ ";keys=comma,%2C,dot,.,semi,%3B",
+ ";keys=comma,%2C,semi,%3B,dot,.",
+ ";keys=dot,.,comma,%2C,semi,%3B",
+ ";keys=dot,.,semi,%3B,comma,%2C",
+ ";keys=semi,%3B,comma,%2C,dot,.",
+ ";keys=semi,%3B,dot,.,comma,%2C"
+ ]],
+ ["{;keys*}", [
+ ";comma=%2C;dot=.;semi=%3B",
+ ";comma=%2C;semi=%3B;dot=.",
+ ";dot=.;comma=%2C;semi=%3B",
+ ";dot=.;semi=%3B;comma=%2C",
+ ";semi=%3B;comma=%2C;dot=.",
+ ";semi=%3B;dot=.;comma=%2C"
+ ]]
+ ]
+ },
+ "3.2.8 Form-Style Query Expansion" :
+ {
+ "variables": {
+ "count" : ["one", "two", "three"],
+ "dom" : ["example", "com"],
+ "dub" : "me/too",
+ "hello" : "Hello World!",
+ "half" : "50%",
+ "var" : "value",
+ "who" : "fred",
+ "base" : "http://example.com/home/",
+ "path" : "/foo/bar",
+ "list" : ["red", "green", "blue"],
+ "keys" : { "semi" : ";", "dot" : ".", "comma" : ","},
+ "v" : "6",
+ "x" : "1024",
+ "y" : "768",
+ "empty" : "",
+ "empty_keys" : [],
+ "undef" : null
+ },
+ "testcases" : [
+ ["{?who}", "?who=fred"],
+ ["{?half}", "?half=50%25"],
+ ["{?x,y}", "?x=1024&y=768"],
+ ["{?x,y,empty}", "?x=1024&y=768&empty="],
+ ["{?x,y,undef}", "?x=1024&y=768"],
+ ["{?var:3}", "?var=val"],
+ ["{?list}", "?list=red,green,blue"],
+ ["{?list*}", "?list=red&list=green&list=blue"],
+ ["{?keys}", [
+ "?keys=comma,%2C,dot,.,semi,%3B",
+ "?keys=comma,%2C,semi,%3B,dot,.",
+ "?keys=dot,.,comma,%2C,semi,%3B",
+ "?keys=dot,.,semi,%3B,comma,%2C",
+ "?keys=semi,%3B,comma,%2C,dot,.",
+ "?keys=semi,%3B,dot,.,comma,%2C"
+ ]],
+ ["{?keys*}", [
+ "?comma=%2C&dot=.&semi=%3B",
+ "?comma=%2C&semi=%3B&dot=.",
+ "?dot=.&comma=%2C&semi=%3B",
+ "?dot=.&semi=%3B&comma=%2C",
+ "?semi=%3B&comma=%2C&dot=.",
+ "?semi=%3B&dot=.&comma=%2C"
+ ]]
+ ]
+ },
+ "3.2.9 Form-Style Query Continuation" :
+ {
+ "variables": {
+ "count" : ["one", "two", "three"],
+ "dom" : ["example", "com"],
+ "dub" : "me/too",
+ "hello" : "Hello World!",
+ "half" : "50%",
+ "var" : "value",
+ "who" : "fred",
+ "base" : "http://example.com/home/",
+ "path" : "/foo/bar",
+ "list" : ["red", "green", "blue"],
+ "keys" : { "semi" : ";", "dot" : ".", "comma" : ","},
+ "v" : "6",
+ "x" : "1024",
+ "y" : "768",
+ "empty" : "",
+ "empty_keys" : [],
+ "undef" : null
+ },
+ "testcases" : [
+ ["{&who}", "&who=fred"],
+ ["{&half}", "&half=50%25"],
+ ["?fixed=yes{&x}", "?fixed=yes&x=1024"],
+ ["{&var:3}", "&var=val"],
+ ["{&x,y,empty}", "&x=1024&y=768&empty="],
+ ["{&x,y,undef}", "&x=1024&y=768"],
+ ["{&list}", "&list=red,green,blue"],
+ ["{&list*}", "&list=red&list=green&list=blue"],
+ ["{&keys}", [
+ "&keys=comma,%2C,dot,.,semi,%3B",
+ "&keys=comma,%2C,semi,%3B,dot,.",
+ "&keys=dot,.,comma,%2C,semi,%3B",
+ "&keys=dot,.,semi,%3B,comma,%2C",
+ "&keys=semi,%3B,comma,%2C,dot,.",
+ "&keys=semi,%3B,dot,.,comma,%2C"
+ ]],
+ ["{&keys*}", [
+ "&comma=%2C&dot=.&semi=%3B",
+ "&comma=%2C&semi=%3B&dot=.",
+ "&dot=.&comma=%2C&semi=%3B",
+ "&dot=.&semi=%3B&comma=%2C",
+ "&semi=%3B&comma=%2C&dot=.",
+ "&semi=%3B&dot=.&comma=%2C"
+ ]]
+ ]
+ }
+}
diff --git a/tests/fixtures/spec-examples.json b/tests/fixtures/spec-examples.json
new file mode 100644
index 0000000..2e8e942
--- /dev/null
+++ b/tests/fixtures/spec-examples.json
@@ -0,0 +1,218 @@
+{
+ "Level 1 Examples" :
+ {
+ "level": 1,
+ "variables": {
+ "var" : "value",
+ "hello" : "Hello World!"
+ },
+ "testcases" : [
+ ["{var}", "value"],
+ ["{hello}", "Hello%20World%21"]
+ ]
+ },
+ "Level 2 Examples" :
+ {
+ "level": 2,
+ "variables": {
+ "var" : "value",
+ "hello" : "Hello World!",
+ "path" : "/foo/bar"
+ },
+ "testcases" : [
+ ["{+var}", "value"],
+ ["{+hello}", "Hello%20World!"],
+ ["{+path}/here", "/foo/bar/here"],
+ ["here?ref={+path}", "here?ref=/foo/bar"]
+ ]
+ },
+ "Level 3 Examples" :
+ {
+ "level": 3,
+ "variables": {
+ "var" : "value",
+ "hello" : "Hello World!",
+ "empty" : "",
+ "path" : "/foo/bar",
+ "x" : "1024",
+ "y" : "768"
+ },
+ "testcases" : [
+ ["map?{x,y}", "map?1024,768"],
+ ["{x,hello,y}", "1024,Hello%20World%21,768"],
+ ["{+x,hello,y}", "1024,Hello%20World!,768"],
+ ["{+path,x}/here", "/foo/bar,1024/here"],
+ ["{#x,hello,y}", "#1024,Hello%20World!,768"],
+ ["{#path,x}/here", "#/foo/bar,1024/here"],
+ ["X{.var}", "X.value"],
+ ["X{.x,y}", "X.1024.768"],
+ ["{/var}", "/value"],
+ ["{/var,x}/here", "/value/1024/here"],
+ ["{;x,y}", ";x=1024;y=768"],
+ ["{;x,y,empty}", ";x=1024;y=768;empty"],
+ ["{?x,y}", "?x=1024&y=768"],
+ ["{?x,y,empty}", "?x=1024&y=768&empty="],
+ ["?fixed=yes{&x}", "?fixed=yes&x=1024"],
+ ["{&x,y,empty}", "&x=1024&y=768&empty="]
+ ]
+ },
+ "Level 4 Examples" :
+ {
+ "level": 4,
+ "variables": {
+ "var": "value",
+ "hello": "Hello World!",
+ "path": "/foo/bar",
+ "list": ["red", "green", "blue"],
+ "keys": {"semi": ";", "dot": ".", "comma":","}
+ },
+ "testcases": [
+ ["{var:3}", "val"],
+ ["{var:30}", "value"],
+ ["{list}", "red,green,blue"],
+ ["{list*}", "red,green,blue"],
+ ["{keys}", [
+ "comma,%2C,dot,.,semi,%3B",
+ "comma,%2C,semi,%3B,dot,.",
+ "dot,.,comma,%2C,semi,%3B",
+ "dot,.,semi,%3B,comma,%2C",
+ "semi,%3B,comma,%2C,dot,.",
+ "semi,%3B,dot,.,comma,%2C"
+ ]],
+ ["{keys*}", [
+ "comma=%2C,dot=.,semi=%3B",
+ "comma=%2C,semi=%3B,dot=.",
+ "dot=.,comma=%2C,semi=%3B",
+ "dot=.,semi=%3B,comma=%2C",
+ "semi=%3B,comma=%2C,dot=.",
+ "semi=%3B,dot=.,comma=%2C"
+ ]],
+ ["{+path:6}/here", "/foo/b/here"],
+ ["{+list}", "red,green,blue"],
+ ["{+list*}", "red,green,blue"],
+ ["{+keys}", [
+ "comma,,,dot,.,semi,;",
+ "comma,,,semi,;,dot,.",
+ "dot,.,comma,,,semi,;",
+ "dot,.,semi,;,comma,,",
+ "semi,;,comma,,,dot,.",
+ "semi,;,dot,.,comma,,"
+ ]],
+ ["{+keys*}", [
+ "comma=,,dot=.,semi=;",
+ "comma=,,semi=;,dot=.",
+ "dot=.,comma=,,semi=;",
+ "dot=.,semi=;,comma=,",
+ "semi=;,comma=,,dot=.",
+ "semi=;,dot=.,comma=,"
+ ]],
+ ["{#path:6}/here", "#/foo/b/here"],
+ ["{#list}", "#red,green,blue"],
+ ["{#list*}", "#red,green,blue"],
+ ["{#keys}", [
+ "#comma,,,dot,.,semi,;",
+ "#comma,,,semi,;,dot,.",
+ "#dot,.,comma,,,semi,;",
+ "#dot,.,semi,;,comma,,",
+ "#semi,;,comma,,,dot,.",
+ "#semi,;,dot,.,comma,,"
+ ]],
+ ["{#keys*}", [
+ "#comma=,,dot=.,semi=;",
+ "#comma=,,semi=;,dot=.",
+ "#dot=.,comma=,,semi=;",
+ "#dot=.,semi=;,comma=,",
+ "#semi=;,comma=,,dot=.",
+ "#semi=;,dot=.,comma=,"
+ ]],
+ ["X{.var:3}", "X.val"],
+ ["X{.list}", "X.red,green,blue"],
+ ["X{.list*}", "X.red.green.blue"],
+ ["X{.keys}", [
+ "X.comma,%2C,dot,.,semi,%3B",
+ "X.comma,%2C,semi,%3B,dot,.",
+ "X.dot,.,comma,%2C,semi,%3B",
+ "X.dot,.,semi,%3B,comma,%2C",
+ "X.semi,%3B,comma,%2C,dot,.",
+ "X.semi,%3B,dot,.,comma,%2C"
+ ]],
+ ["{/var:1,var}", "/v/value"],
+ ["{/list}", "/red,green,blue"],
+ ["{/list*}", "/red/green/blue"],
+ ["{/list*,path:4}", "/red/green/blue/%2Ffoo"],
+ ["{/keys}", [
+ "/comma,%2C,dot,.,semi,%3B",
+ "/comma,%2C,semi,%3B,dot,.",
+ "/dot,.,comma,%2C,semi,%3B",
+ "/dot,.,semi,%3B,comma,%2C",
+ "/semi,%3B,comma,%2C,dot,.",
+ "/semi,%3B,dot,.,comma,%2C"
+ ]],
+ ["{/keys*}", [
+ "/comma=%2C/dot=./semi=%3B",
+ "/comma=%2C/semi=%3B/dot=.",
+ "/dot=./comma=%2C/semi=%3B",
+ "/dot=./semi=%3B/comma=%2C",
+ "/semi=%3B/comma=%2C/dot=.",
+ "/semi=%3B/dot=./comma=%2C"
+ ]],
+ ["{;hello:5}", ";hello=Hello"],
+ ["{;list}", ";list=red,green,blue"],
+ ["{;list*}", ";list=red;list=green;list=blue"],
+ ["{;keys}", [
+ ";keys=comma,%2C,dot,.,semi,%3B",
+ ";keys=comma,%2C,semi,%3B,dot,.",
+ ";keys=dot,.,comma,%2C,semi,%3B",
+ ";keys=dot,.,semi,%3B,comma,%2C",
+ ";keys=semi,%3B,comma,%2C,dot,.",
+ ";keys=semi,%3B,dot,.,comma,%2C"
+ ]],
+ ["{;keys*}", [
+ ";comma=%2C;dot=.;semi=%3B",
+ ";comma=%2C;semi=%3B;dot=.",
+ ";dot=.;comma=%2C;semi=%3B",
+ ";dot=.;semi=%3B;comma=%2C",
+ ";semi=%3B;comma=%2C;dot=.",
+ ";semi=%3B;dot=.;comma=%2C"
+ ]],
+ ["{?var:3}", "?var=val"],
+ ["{?list}", "?list=red,green,blue"],
+ ["{?list*}", "?list=red&list=green&list=blue"],
+ ["{?keys}", [
+ "?keys=comma,%2C,dot,.,semi,%3B",
+ "?keys=comma,%2C,semi,%3B,dot,.",
+ "?keys=dot,.,comma,%2C,semi,%3B",
+ "?keys=dot,.,semi,%3B,comma,%2C",
+ "?keys=semi,%3B,comma,%2C,dot,.",
+ "?keys=semi,%3B,dot,.,comma,%2C"
+ ]],
+ ["{?keys*}", [
+ "?comma=%2C&dot=.&semi=%3B",
+ "?comma=%2C&semi=%3B&dot=.",
+ "?dot=.&comma=%2C&semi=%3B",
+ "?dot=.&semi=%3B&comma=%2C",
+ "?semi=%3B&comma=%2C&dot=.",
+ "?semi=%3B&dot=.&comma=%2C"
+ ]],
+ ["{&var:3}", "&var=val"],
+ ["{&list}", "&list=red,green,blue"],
+ ["{&list*}", "&list=red&list=green&list=blue"],
+ ["{&keys}", [
+ "&keys=comma,%2C,dot,.,semi,%3B",
+ "&keys=comma,%2C,semi,%3B,dot,.",
+ "&keys=dot,.,comma,%2C,semi,%3B",
+ "&keys=dot,.,semi,%3B,comma,%2C",
+ "&keys=semi,%3B,comma,%2C,dot,.",
+ "&keys=semi,%3B,dot,.,comma,%2C"
+ ]],
+ ["{&keys*}", [
+ "&comma=%2C&dot=.&semi=%3B",
+ "&comma=%2C&semi=%3B&dot=.",
+ "&dot=.&comma=%2C&semi=%3B",
+ "&dot=.&semi=%3B&comma=%2C",
+ "&semi=%3B&comma=%2C&dot=.",
+ "&semi=%3B&dot=.&comma=%2C"
+ ]]
+ ]
+ }
+}
diff --git a/tests/fixtures/transform-json-tests.xslt b/tests/fixtures/transform-json-tests.xslt
new file mode 100644
index 0000000..d956b6b
--- /dev/null
+++ b/tests/fixtures/transform-json-tests.xslt
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+ <xsl:include href="json2xml.xslt"/>
+
+ <!-- the input test files need to be supplied as a sequence of file names only (no extension) -->
+ <xsl:param name="tests" select="('extended-tests', 'negative-tests', 'spec-examples-by-section', 'spec-examples')"/>
+ <xsl:param name="json-ext" select="'json'"/>
+ <xsl:param name="xml-ext" select="'xml'"/>
+
+ <!-- run this stylesheet with any input to generate the XML test files at output. -->
+ <!-- (a popular way of doing this is to supply the XSLT to itself as the input file.) -->
+
+ <xsl:template match="/">
+ <xsl:for-each select="$tests">
+ <xsl:variable name="file" select="current()"/>
+ <xsl:variable name="json">
+ <json>
+ <xsl:value-of select="unparsed-text(concat($file, '.', $json-ext))"/>
+ </json>
+ </xsl:variable>
+ <xsl:variable name="xml">
+ <xsl:apply-templates select="$json/json"/>
+ </xsl:variable>
+ <xsl:result-document href="{$file}.{$xml-ext}" method="xml" indent="yes">
+ <tests>
+ <xsl:for-each select="$xml/json/object/field">
+ <test name="{@name}">
+ <xsl:if test="exists(object/field[1][@name eq 'level'])">
+ <xsl:attribute name="level" select="object/field[1][@name eq 'level']/integer/text()"/>
+ </xsl:if>
+ <variables>
+ <xsl:for-each select="object/field[@name eq 'variables']/object/field">
+ <variable name="{@name}">
+ <xsl:copy-of select="*"/>
+ </variable>
+ </xsl:for-each>
+ </variables>
+ <testcases>
+ <xsl:for-each select="object/field[@name eq 'testcases']/array/array">
+ <testcase template="{*[1]}" result="{*[2]}"/>
+ </xsl:for-each> </testcases>
+ </test>
+ </xsl:for-each>
+ </tests>
+ </xsl:result-document>
+ </xsl:for-each>
+ </xsl:template>
+
+</xsl:stylesheet> \ No newline at end of file
diff --git a/tests/test_from_fixtures.py b/tests/test_from_fixtures.py
new file mode 100644
index 0000000..f4ae1b8
--- /dev/null
+++ b/tests/test_from_fixtures.py
@@ -0,0 +1,119 @@
+import json
+import os.path
+
+import pytest
+
+import uritemplate
+
+
+def fixture_file_path(filename):
+ absolute_dir = os.path.abspath(os.path.dirname(__file__))
+ filename = filename + '.json'
+ return os.path.join(absolute_dir, 'fixtures', filename)
+
+
+def load_examples(filename):
+ path = fixture_file_path(filename)
+ with open(path, 'r') as examples_file:
+ examples = json.load(examples_file)
+ return examples
+
+
+def expected_set(expected):
+ if isinstance(expected, list):
+ return set(expected)
+ return set([expected])
+
+
+class FixtureMixin(object):
+ def _get_test(self, section):
+ test = self.examples.get(section, {})
+ return test.get('variables', {}), test.get('testcases', [])
+
+ def _test(self, testname):
+ variables, testcases = self._get_test(testname)
+ for template, expected in testcases:
+ expected = expected_set(expected)
+ expanded = uritemplate.expand(template, variables)
+ assert expanded in expected
+
+
+class TestSpecExamples(FixtureMixin):
+ examples = load_examples('spec-examples')
+
+ def test_level_1(self):
+ """Check that uritemplate.expand matches Level 1 expectations."""
+ self._test('Level 1 Examples')
+
+ def test_level_2(self):
+ """Check that uritemplate.expand matches Level 2 expectations."""
+ self._test('Level 2 Examples')
+
+ def test_level_3(self):
+ """Check that uritemplate.expand matches Level 3 expectations."""
+ self._test('Level 3 Examples')
+
+ def test_level_4(self):
+ """Check that uritemplate.expand matches Level 4 expectations."""
+ self._test('Level 4 Examples')
+
+
+class TestSpecExamplesByRFCSection(FixtureMixin):
+ examples = load_examples('spec-examples-by-section')
+
+ def test_variable_expansion(self):
+ """Check variable expansion."""
+ self._test('3.2.1 Variable Expansion')
+
+ def test_simple_string_expansion(self):
+ """Check simple string expansion."""
+ self._test('3.2.2 Simple String Expansion')
+
+ def test_reserved_expansion(self):
+ """Check reserved expansion."""
+ self._test('3.2.3 Reserved Expansion')
+
+ def test_fragment_expansion(self):
+ """Check fragment expansion."""
+ self._test('3.2.4 Fragment Expansion')
+
+ def test_dot_prefixed_label_expansion(self):
+ """Check label expansion with dot-prefix."""
+ self._test('3.2.5 Label Expansion with Dot-Prefix')
+
+ def test_path_segment_expansion(self):
+ """Check path segment expansion."""
+ self._test('3.2.6 Path Segment Expansion')
+
+ def test_path_style_parameter_expansion(self):
+ """Check path-style param expansion."""
+ self._test('3.2.7 Path-Style Parameter Expansion')
+
+ def test_form_style_query_expansion(self):
+ """Check form-style query expansion."""
+ self._test('3.2.8 Form-Style Query Expansion')
+
+ def test_form_style_query_cntinuation(self):
+ """Check form-style query continuation."""
+ self._test('3.2.9 Form-Style Query Continuation')
+
+
+class TestExtendedTests(FixtureMixin):
+ examples = load_examples('extended-tests')
+
+ @pytest.mark.xfail(reason='See bug #17')
+ def test_additional_examples_1(self):
+ """Check Additional Examples 1."""
+ self._test('Additional Examples 1')
+
+ def test_additional_examples_2(self):
+ """Check Additional Examples 2."""
+ self._test('Additional Examples 2')
+
+ def test_additional_examples_3(self):
+ """Check Additional Examples 3."""
+ self._test('Additional Examples 3: Empty Variables')
+
+ def test_additional_examples_4(self):
+ """Check Additional Examples 4."""
+ self._test('Additional Examples 4: Numeric Keys')
diff --git a/tests/test_uritemplate.py b/tests/test_uritemplate.py
new file mode 100644
index 0000000..26aeb72
--- /dev/null
+++ b/tests/test_uritemplate.py
@@ -0,0 +1,585 @@
+from unittest import TestCase, main
+from uritemplate import URITemplate, expand, partial
+from uritemplate import variable
+
+
+def merge_dicts(*args):
+ d = {}
+ for arg in args:
+ d.update(arg)
+ return d
+
+
+class RFCTemplateExamples(type):
+ var = {'var': 'value'}
+ hello = {'hello': 'Hello World!'}
+ path = {'path': '/foo/bar'}
+ x = {'x': '1024'}
+ y = {'y': '768'}
+ empty = {'empty': ''}
+ merged_x_y = merge_dicts(x, y)
+ list_ex = {'list': ['red', 'green', 'blue']}
+ keys = {'keys': [('semi', ';'), ('dot', '.'), ('comma', ',')]}
+
+ # # Level 1
+ # Simple string expansion
+ level1_examples = {
+ '{var}': {
+ 'expansion': var,
+ 'expected': 'value',
+ },
+ '{hello}': {
+ 'expansion': hello,
+ 'expected': 'Hello%20World%21',
+ },
+ }
+
+ # # Level 2
+ # Reserved string expansion
+ level2_reserved_examples = {
+ '{+var}': {
+ 'expansion': var,
+ 'expected': 'value',
+ },
+ '{+hello}': {
+ 'expansion': hello,
+ 'expected': 'Hello%20World!',
+ },
+ '{+path}/here': {
+ 'expansion': path,
+ 'expected': '/foo/bar/here',
+ },
+ 'here?ref={+path}': {
+ 'expansion': path,
+ 'expected': 'here?ref=/foo/bar',
+ },
+ }
+
+ # Fragment expansion, crosshatch-prefixed
+ level2_fragment_examples = {
+ 'X{#var}': {
+ 'expansion': var,
+ 'expected': 'X#value',
+ },
+ 'X{#hello}': {
+ 'expansion': hello,
+ 'expected': 'X#Hello%20World!'
+ },
+ }
+
+ # # Level 3
+ # String expansion with multiple variables
+ level3_multiple_variable_examples = {
+ 'map?{x,y}': {
+ 'expansion': merged_x_y,
+ 'expected': 'map?1024,768',
+ },
+ '{x,hello,y}': {
+ 'expansion': merge_dicts(x, y, hello),
+ 'expected': '1024,Hello%20World%21,768',
+ },
+ }
+
+ # Reserved expansion with multiple variables
+ level3_reserved_examples = {
+ '{+x,hello,y}': {
+ 'expansion': merge_dicts(x, y, hello),
+ 'expected': '1024,Hello%20World!,768',
+ },
+ '{+path,x}/here': {
+ 'expansion': merge_dicts(path, x),
+ 'expected': '/foo/bar,1024/here',
+ },
+ }
+
+ # Fragment expansion with multiple variables
+ level3_fragment_examples = {
+ '{#x,hello,y}': {
+ 'expansion': merge_dicts(x, y, hello),
+ 'expected': '#1024,Hello%20World!,768',
+ },
+ '{#path,x}/here': {
+ 'expansion': merge_dicts(path, x),
+ 'expected': '#/foo/bar,1024/here'
+ },
+ }
+
+ # Label expansion, dot-prefixed
+ level3_label_examples = {
+ 'X{.var}': {
+ 'expansion': var,
+ 'expected': 'X.value',
+ },
+ 'X{.x,y}': {
+ 'expansion': merged_x_y,
+ 'expected': 'X.1024.768',
+ }
+ }
+
+ # Path segments, slash-prefixed
+ level3_path_segment_examples = {
+ '{/var}': {
+ 'expansion': var,
+ 'expected': '/value',
+ },
+ '{/var,x}/here': {
+ 'expansion': merge_dicts(var, x),
+ 'expected': '/value/1024/here',
+ },
+ }
+
+ # Path-style parameters, semicolon-prefixed
+ level3_path_semi_examples = {
+ '{;x,y}': {
+ 'expansion': merged_x_y,
+ 'expected': ';x=1024;y=768',
+ },
+ '{;x,y,empty}': {
+ 'expansion': merge_dicts(x, y, empty),
+ 'expected': ';x=1024;y=768;empty',
+ },
+ }
+
+ # Form-style query, ampersand-separated
+ level3_form_amp_examples = {
+ '{?x,y}': {
+ 'expansion': merged_x_y,
+ 'expected': '?x=1024&y=768',
+ },
+ '{?x,y,empty}': {
+ 'expansion': merge_dicts(x, y, empty),
+ 'expected': '?x=1024&y=768&empty=',
+ },
+ }
+
+ # Form-style query continuation
+ level3_form_cont_examples = {
+ '?fixed=yes{&x}': {
+ 'expansion': x,
+ 'expected': '?fixed=yes&x=1024',
+ },
+ '{&x,y,empty}': {
+ 'expansion': merge_dicts(x, y, empty),
+ 'expected': '&x=1024&y=768&empty=',
+ }
+ }
+
+ # # Level 4
+ # String expansion with value modifiers
+ level4_value_modifier_examples = {
+ '{var:3}': {
+ 'expansion': var,
+ 'expected': 'val',
+ },
+ '{var:30}': {
+ 'expansion': var,
+ 'expected': 'value',
+ },
+ '{list}': {
+ 'expansion': list_ex,
+ 'expected': 'red,green,blue',
+ },
+ '{list*}': {
+ 'expansion': list_ex,
+ 'expected': 'red,green,blue',
+ },
+ '{keys}': {
+ 'expansion': keys,
+ 'expected': 'semi,%3B,dot,.,comma,%2C',
+ },
+ '{keys*}': {
+ 'expansion': keys,
+ 'expected': 'semi=%3B,dot=.,comma=%2C',
+ },
+ }
+
+ # Reserved expansion with value modifiers
+ level4_reserved_examples = {
+ '{+path:6}/here': {
+ 'expansion': path,
+ 'expected': '/foo/b/here',
+ },
+ '{+list}': {
+ 'expansion': list_ex,
+ 'expected': 'red,green,blue',
+ },
+ '{+list*}': {
+ 'expansion': list_ex,
+ 'expected': 'red,green,blue',
+ },
+ '{+keys}': {
+ 'expansion': keys,
+ 'expected': 'semi,;,dot,.,comma,,',
+ },
+ '{+keys*}': {
+ 'expansion': keys,
+ 'expected': 'semi=;,dot=.,comma=,',
+ },
+ }
+
+ # Fragment expansion with value modifiers
+ level4_fragment_examples = {
+ '{#path:6}/here': {
+ 'expansion': path,
+ 'expected': '#/foo/b/here',
+ },
+ '{#list}': {
+ 'expansion': list_ex,
+ 'expected': '#red,green,blue',
+ },
+ '{#list*}': {
+ 'expansion': list_ex,
+ 'expected': '#red,green,blue',
+ },
+ '{#keys}': {
+ 'expansion': keys,
+ 'expected': '#semi,;,dot,.,comma,,'
+ },
+ '{#keys*}': {
+ 'expansion': keys,
+ 'expected': '#semi=;,dot=.,comma=,'
+ },
+ }
+
+ # Label expansion, dot-prefixed
+ level4_label_examples = {
+ 'X{.var:3}': {
+ 'expansion': var,
+ 'expected': 'X.val',
+ },
+ 'X{.list}': {
+ 'expansion': list_ex,
+ 'expected': 'X.red,green,blue',
+ },
+ 'X{.list*}': {
+ 'expansion': list_ex,
+ 'expected': 'X.red.green.blue',
+ },
+ 'X{.keys}': {
+ 'expansion': keys,
+ 'expected': 'X.semi,%3B,dot,.,comma,%2C',
+ },
+ 'X{.keys*}': {
+ 'expansion': keys,
+ 'expected': 'X.semi=%3B.dot=..comma=%2C',
+ },
+ }
+
+ # Path segments, slash-prefixed
+ level4_path_slash_examples = {
+ '{/var:1,var}': {
+ 'expansion': var,
+ 'expected': '/v/value',
+ },
+ '{/list}': {
+ 'expansion': list_ex,
+ 'expected': '/red,green,blue',
+ },
+ '{/list*}': {
+ 'expansion': list_ex,
+ 'expected': '/red/green/blue',
+ },
+ '{/list*,path:4}': {
+ 'expansion': merge_dicts(list_ex, path),
+ 'expected': '/red/green/blue/%2Ffoo',
+ },
+ '{/keys}': {
+ 'expansion': keys,
+ 'expected': '/semi,%3B,dot,.,comma,%2C',
+ },
+ '{/keys*}': {
+ 'expansion': keys,
+ 'expected': '/semi=%3B/dot=./comma=%2C',
+ },
+ }
+
+ # Path-style parameters, semicolon-prefixed
+ level4_path_semi_examples = {
+ '{;hello:5}': {
+ 'expansion': hello,
+ 'expected': ';hello=Hello',
+ },
+ '{;list}': {
+ 'expansion': list_ex,
+ 'expected': ';list=red,green,blue',
+ },
+ '{;list*}': {
+ 'expansion': list_ex,
+ 'expected': ';list=red;list=green;list=blue',
+ },
+ '{;keys}': {
+ 'expansion': keys,
+ 'expected': ';keys=semi,%3B,dot,.,comma,%2C',
+ },
+ '{;keys*}': {
+ 'expansion': keys,
+ 'expected': ';semi=%3B;dot=.;comma=%2C',
+ },
+ }
+
+ # Form-style query, ampersand-separated
+ level4_form_amp_examples = {
+ '{?var:3}': {
+ 'expansion': var,
+ 'expected': '?var=val',
+ },
+ '{?list}': {
+ 'expansion': list_ex,
+ 'expected': '?list=red,green,blue',
+ },
+ '{?list*}': {
+ 'expansion': list_ex,
+ 'expected': '?list=red&list=green&list=blue',
+ },
+ '{?keys}': {
+ 'expansion': keys,
+ 'expected': '?keys=semi,%3B,dot,.,comma,%2C',
+ },
+ '{?keys*}': {
+ 'expansion': keys,
+ 'expected': '?semi=%3B&dot=.&comma=%2C',
+ },
+ }
+
+ # Form-style query continuation
+ level4_form_query_examples = {
+ '{&var:3}': {
+ 'expansion': var,
+ 'expected': '&var=val',
+ },
+ '{&list}': {
+ 'expansion': list_ex,
+ 'expected': '&list=red,green,blue',
+ },
+ '{&list*}': {
+ 'expansion': list_ex,
+ 'expected': '&list=red&list=green&list=blue',
+ },
+ '{&keys}': {
+ 'expansion': keys,
+ 'expected': '&keys=semi,%3B,dot,.,comma,%2C',
+ },
+ '{&keys*}': {
+ 'expansion': keys,
+ 'expected': '&semi=%3B&dot=.&comma=%2C',
+ },
+ }
+
+ def __new__(cls, name, bases, attrs):
+ def make_test(d):
+ def _test_(self):
+ for k, v in d.items():
+ t = URITemplate(k)
+ self.assertEqual(t.expand(v['expansion']), v['expected'])
+ return _test_
+
+ examples = [
+ (
+ n, getattr(RFCTemplateExamples, n)
+ ) for n in dir(RFCTemplateExamples) if n.startswith('level')
+ ]
+
+ for name, value in examples:
+ testname = 'test_%s' % name
+ attrs[testname] = make_test(value)
+
+ return type.__new__(cls, name, bases, attrs)
+
+
+class TestURITemplate(RFCTemplateExamples('RFCMeta', (TestCase,), {})):
+ def test_no_variables_in_uri(self):
+ """
+ This test ensures that if there are no variables present, the
+ template evaluates to itself.
+ """
+ uri = 'https://api.github.com/users'
+ t = URITemplate(uri)
+ self.assertEqual(t.expand(), uri)
+ self.assertEqual(t.expand(users='foo'), uri)
+
+ def test_all_variables_parsed(self):
+ """
+ This test ensures that all variables are parsed.
+ """
+ uris = [
+ 'https://api.github.com',
+ 'https://api.github.com/users{/user}',
+ 'https://api.github.com/repos{/user}{/repo}',
+ 'https://api.github.com/repos{/user}{/repo}/issues{/issue}'
+ ]
+
+ for i, uri in enumerate(uris):
+ t = URITemplate(uri)
+ self.assertEqual(len(t.variables), i)
+
+ def test_expand(self):
+ """
+ This test ensures that expansion works as expected.
+ """
+ # Single
+ t = URITemplate('https://api.github.com/users{/user}')
+ expanded = 'https://api.github.com/users/sigmavirus24'
+ self.assertEqual(t.expand(user='sigmavirus24'), expanded)
+ v = t.variables[0]
+ self.assertEqual(v.expand({'user': None}), {'/user': ''})
+
+ # Multiple
+ t = URITemplate('https://api.github.com/users{/user}{/repo}')
+ expanded = 'https://api.github.com/users/sigmavirus24/github3.py'
+ self.assertEqual(
+ t.expand({'repo': 'github3.py'}, user='sigmavirus24'),
+ expanded
+ )
+
+ def test_str_repr(self):
+ uri = 'https://api.github.com{/endpoint}'
+ t = URITemplate(uri)
+ self.assertEqual(str(t), uri)
+ self.assertEqual(str(t.variables[0]), '/endpoint')
+ self.assertEqual(repr(t), 'URITemplate("%s")' % uri)
+ self.assertEqual(repr(t.variables[0]), 'URIVariable(/endpoint)')
+
+ def test_hash(self):
+ uri = 'https://api.github.com{/endpoint}'
+ self.assertEqual(hash(URITemplate(uri)), hash(uri))
+
+ def test_default_value(self):
+ uri = 'https://api.github.com/user{/user=sigmavirus24}'
+ t = URITemplate(uri)
+ self.assertEqual(t.expand(),
+ 'https://api.github.com/user/sigmavirus24')
+ self.assertEqual(t.expand(user='lukasa'),
+ 'https://api.github.com/user/lukasa')
+
+ def test_query_expansion(self):
+ t = URITemplate('{foo}')
+ self.assertEqual(
+ t.variables[0]._query_expansion('foo', None, False, False), None
+ )
+
+ def test_label_path_expansion(self):
+ t = URITemplate('{foo}')
+ self.assertEqual(
+ t.variables[0]._label_path_expansion('foo', None, False, False),
+ None
+ )
+
+ def test_semi_path_expansion(self):
+ t = URITemplate('{foo}')
+ v = t.variables[0]
+ self.assertEqual(
+ v._semi_path_expansion('foo', None, False, False),
+ None
+ )
+ t.variables[0].operator = '?'
+ self.assertEqual(
+ v._semi_path_expansion('foo', ['bar', 'bogus'], True, False),
+ 'foo=bar&foo=bogus'
+ )
+
+ def test_string_expansion(self):
+ t = URITemplate('{foo}')
+ self.assertEqual(
+ t.variables[0]._string_expansion('foo', None, False, False),
+ None
+ )
+
+ def test_hashability(self):
+ t = URITemplate('{foo}')
+ u = URITemplate('{foo}')
+ d = {t: 1}
+ d[u] += 1
+ self.assertEqual(d, {t: 2})
+
+ def test_no_mutate(self):
+ args = {}
+ t = URITemplate('')
+ t.expand(args, key=1)
+ self.assertEqual(args, {})
+
+
+class TestURIVariable(TestCase):
+ def setUp(self):
+ self.v = variable.URIVariable('{foo}')
+
+ def test_post_parse(self):
+ v = self.v
+ self.assertEqual(v.join_str, ',')
+ self.assertEqual(v.operator, '')
+ self.assertEqual(v.safe, '')
+ self.assertEqual(v.start, '')
+
+ def test_post_parse_plus(self):
+ v = self.v
+ v.operator = '+'
+ v.post_parse()
+ self.assertEqual(v.join_str, ',')
+ self.assertEqual(v.safe, variable.URIVariable.reserved)
+ self.assertEqual(v.start, '')
+
+ def test_post_parse_octothorpe(self):
+ v = self.v
+ v.operator = '#'
+ v.post_parse()
+ self.assertEqual(v.join_str, ',')
+ self.assertEqual(v.safe, variable.URIVariable.reserved)
+ self.assertEqual(v.start, '#')
+
+ def test_post_parse_question(self):
+ v = self.v
+ v.operator = '?'
+ v.post_parse()
+ self.assertEqual(v.join_str, '&')
+ self.assertEqual(v.safe, '')
+ self.assertEqual(v.start, '?')
+
+ def test_post_parse_ampersand(self):
+ v = self.v
+ v.operator = '&'
+ v.post_parse()
+ self.assertEqual(v.join_str, '&')
+ self.assertEqual(v.safe, '')
+ self.assertEqual(v.start, '&')
+
+
+class TestVariableModule(TestCase):
+ def test_is_list_of_tuples(self):
+ l = [(1, 2), (3, 4)]
+ self.assertEqual(variable.is_list_of_tuples(l), (True, l))
+
+ l = [1, 2, 3, 4]
+ self.assertEqual(variable.is_list_of_tuples(l), (False, None))
+
+ def test_list_test(self):
+ l = [1, 2, 3, 4]
+ self.assertEqual(variable.list_test(l), True)
+
+ l = str([1, 2, 3, 4])
+ self.assertEqual(variable.list_test(l), False)
+
+ def test_list_of_tuples_test(self):
+ l = [(1, 2), (3, 4)]
+ self.assertEqual(variable.dict_test(l), False)
+
+ d = dict(l)
+ self.assertEqual(variable.dict_test(d), True)
+
+
+class TestAPI(TestCase):
+ uri = 'https://api.github.com{/endpoint}'
+
+ def test_expand(self):
+ self.assertEqual(expand(self.uri, {'endpoint': 'users'}),
+ 'https://api.github.com/users')
+
+ def test_partial(self):
+ self.assertEqual(partial(self.uri), URITemplate(self.uri))
+ uri = self.uri + '/sigmavirus24{/other}'
+ self.assertEqual(
+ partial(uri, endpoint='users'),
+ URITemplate('https://api.github.com/users/sigmavirus24{/other}')
+ )
+
+
+if __name__ == '__main__':
+ main()