blob: bd8ab12f570cd38c0a953c85134a8c6a2b309477 [file] [log] [blame]
# coding: utf-8
# Copyright (C) Bob Aman
#
# 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.
require "spec_helper"
require "bigdecimal"
require "addressable/template"
shared_examples_for 'expands' do |tests|
tests.each do |template, expansion|
exp = expansion.is_a?(Array) ? expansion.first : expansion
it "#{template} to #{exp}" do
tmpl = Addressable::Template.new(template).expand(subject)
if expansion.is_a?(Array)
expect(expansion.any?{|i| i == tmpl.to_str}).to be true
else
expect(tmpl.to_str).to eq(expansion)
end
end
end
end
describe "eql?" do
let(:template) { Addressable::Template.new('https://www.example.com/{foo}') }
it 'is equal when the pattern matches' do
other_template = Addressable::Template.new('https://www.example.com/{foo}')
expect(template).to be_eql(other_template)
expect(other_template).to be_eql(template)
end
it 'is not equal when the pattern differs' do
other_template = Addressable::Template.new('https://www.example.com/{bar}')
expect(template).to_not be_eql(other_template)
expect(other_template).to_not be_eql(template)
end
it 'is not equal to non-templates' do
uri = 'https://www.example.com/foo/bar'
addressable_template = Addressable::Template.new uri
addressable_uri = Addressable::URI.parse uri
expect(addressable_template).to_not be_eql(addressable_uri)
expect(addressable_uri).to_not be_eql(addressable_template)
end
end
describe "==" do
let(:template) { Addressable::Template.new('https://www.example.com/{foo}') }
it 'is equal when the pattern matches' do
other_template = Addressable::Template.new('https://www.example.com/{foo}')
expect(template).to eq other_template
expect(other_template).to eq template
end
it 'is not equal when the pattern differs' do
other_template = Addressable::Template.new('https://www.example.com/{bar}')
expect(template).not_to eq other_template
expect(other_template).not_to eq template
end
it 'is not equal to non-templates' do
uri = 'https://www.example.com/foo/bar'
addressable_template = Addressable::Template.new uri
addressable_uri = Addressable::URI.parse uri
expect(addressable_template).not_to eq addressable_uri
expect(addressable_uri).not_to eq addressable_template
end
end
describe "Type conversion" do
subject {
{
:var => true,
:hello => 1234,
:nothing => nil,
:sym => :symbolic,
:decimal => BigDecimal.new('1')
}
}
it_behaves_like 'expands', {
'{var}' => 'true',
'{hello}' => '1234',
'{nothing}' => '',
'{sym}' => 'symbolic',
'{decimal}' => RUBY_VERSION < '2.4.0' ? '0.1E1' : '0.1e1'
}
end
describe "Level 1:" do
subject {
{:var => "value", :hello => "Hello World!"}
}
it_behaves_like 'expands', {
'{var}' => 'value',
'{hello}' => 'Hello%20World%21'
}
end
describe "Level 2" do
subject {
{
:var => "value",
:hello => "Hello World!",
:path => "/foo/bar"
}
}
context "Operator +:" do
it_behaves_like 'expands', {
'{+var}' => 'value',
'{+hello}' => 'Hello%20World!',
'{+path}/here' => '/foo/bar/here',
'here?ref={+path}' => 'here?ref=/foo/bar'
}
end
context "Operator #:" do
it_behaves_like 'expands', {
'X{#var}' => 'X#value',
'X{#hello}' => 'X#Hello%20World!'
}
end
end
describe "Level 3" do
subject {
{
:var => "value",
:hello => "Hello World!",
:empty => "",
:path => "/foo/bar",
:x => "1024",
:y => "768"
}
}
context "Operator nil (multiple vars):" do
it_behaves_like 'expands', {
'map?{x,y}' => 'map?1024,768',
'{x,hello,y}' => '1024,Hello%20World%21,768'
}
end
context "Operator + (multiple vars):" do
it_behaves_like 'expands', {
'{+x,hello,y}' => '1024,Hello%20World!,768',
'{+path,x}/here' => '/foo/bar,1024/here'
}
end
context "Operator # (multiple vars):" do
it_behaves_like 'expands', {
'{#x,hello,y}' => '#1024,Hello%20World!,768',
'{#path,x}/here' => '#/foo/bar,1024/here'
}
end
context "Operator ." do
it_behaves_like 'expands', {
'X{.var}' => 'X.value',
'X{.x,y}' => 'X.1024.768'
}
end
context "Operator /" do
it_behaves_like 'expands', {
'{/var}' => '/value',
'{/var,x}/here' => '/value/1024/here'
}
end
context "Operator ;" do
it_behaves_like 'expands', {
'{;x,y}' => ';x=1024;y=768',
'{;x,y,empty}' => ';x=1024;y=768;empty'
}
end
context "Operator ?" do
it_behaves_like 'expands', {
'{?x,y}' => '?x=1024&y=768',
'{?x,y,empty}' => '?x=1024&y=768&empty='
}
end
context "Operator &" do
it_behaves_like 'expands', {
'?fixed=yes{&x}' => '?fixed=yes&x=1024',
'{&x,y,empty}' => '&x=1024&y=768&empty='
}
end
end
describe "Level 4" do
subject {
{
:var => "value",
:hello => "Hello World!",
:path => "/foo/bar",
:semi => ";",
:list => %w(red green blue),
:keys => {"semi" => ';', "dot" => '.', "comma" => ','}
}
}
context "Expansion with value modifiers" do
it_behaves_like 'expands', {
'{var:3}' => 'val',
'{var:30}' => 'value',
'{list}' => 'red,green,blue',
'{list*}' => 'red,green,blue',
'{keys}' => [
'semi,%3B,dot,.,comma,%2C',
'dot,.,semi,%3B,comma,%2C',
'comma,%2C,semi,%3B,dot,.',
'semi,%3B,comma,%2C,dot,.',
'dot,.,comma,%2C,semi,%3B',
'comma,%2C,dot,.,semi,%3B'
],
'{keys*}' => [
'semi=%3B,dot=.,comma=%2C',
'dot=.,semi=%3B,comma=%2C',
'comma=%2C,semi=%3B,dot=.',
'semi=%3B,comma=%2C,dot=.',
'dot=.,comma=%2C,semi=%3B',
'comma=%2C,dot=.,semi=%3B'
]
}
end
context "Operator + with value modifiers" do
it_behaves_like 'expands', {
'{+path:6}/here' => '/foo/b/here',
'{+list}' => 'red,green,blue',
'{+list*}' => 'red,green,blue',
'{+keys}' => [
'semi,;,dot,.,comma,,',
'dot,.,semi,;,comma,,',
'comma,,,semi,;,dot,.',
'semi,;,comma,,,dot,.',
'dot,.,comma,,,semi,;',
'comma,,,dot,.,semi,;'
],
'{+keys*}' => [
'semi=;,dot=.,comma=,',
'dot=.,semi=;,comma=,',
'comma=,,semi=;,dot=.',
'semi=;,comma=,,dot=.',
'dot=.,comma=,,semi=;',
'comma=,,dot=.,semi=;'
]
}
end
context "Operator # with value modifiers" do
it_behaves_like 'expands', {
'{#path:6}/here' => '#/foo/b/here',
'{#list}' => '#red,green,blue',
'{#list*}' => '#red,green,blue',
'{#keys}' => [
'#semi,;,dot,.,comma,,',
'#dot,.,semi,;,comma,,',
'#comma,,,semi,;,dot,.',
'#semi,;,comma,,,dot,.',
'#dot,.,comma,,,semi,;',
'#comma,,,dot,.,semi,;'
],
'{#keys*}' => [
'#semi=;,dot=.,comma=,',
'#dot=.,semi=;,comma=,',
'#comma=,,semi=;,dot=.',
'#semi=;,comma=,,dot=.',
'#dot=.,comma=,,semi=;',
'#comma=,,dot=.,semi=;'
]
}
end
context "Operator . with value modifiers" do
it_behaves_like 'expands', {
'X{.var:3}' => 'X.val',
'X{.list}' => 'X.red,green,blue',
'X{.list*}' => 'X.red.green.blue',
'X{.keys}' => [
'X.semi,%3B,dot,.,comma,%2C',
'X.dot,.,semi,%3B,comma,%2C',
'X.comma,%2C,semi,%3B,dot,.',
'X.semi,%3B,comma,%2C,dot,.',
'X.dot,.,comma,%2C,semi,%3B',
'X.comma,%2C,dot,.,semi,%3B'
],
'X{.keys*}' => [
'X.semi=%3B.dot=..comma=%2C',
'X.dot=..semi=%3B.comma=%2C',
'X.comma=%2C.semi=%3B.dot=.',
'X.semi=%3B.comma=%2C.dot=.',
'X.dot=..comma=%2C.semi=%3B',
'X.comma=%2C.dot=..semi=%3B'
]
}
end
context "Operator / with value modifiers" do
it_behaves_like 'expands', {
'{/var:1,var}' => '/v/value',
'{/list}' => '/red,green,blue',
'{/list*}' => '/red/green/blue',
'{/list*,path:4}' => '/red/green/blue/%2Ffoo',
'{/keys}' => [
'/semi,%3B,dot,.,comma,%2C',
'/dot,.,semi,%3B,comma,%2C',
'/comma,%2C,semi,%3B,dot,.',
'/semi,%3B,comma,%2C,dot,.',
'/dot,.,comma,%2C,semi,%3B',
'/comma,%2C,dot,.,semi,%3B'
],
'{/keys*}' => [
'/semi=%3B/dot=./comma=%2C',
'/dot=./semi=%3B/comma=%2C',
'/comma=%2C/semi=%3B/dot=.',
'/semi=%3B/comma=%2C/dot=.',
'/dot=./comma=%2C/semi=%3B',
'/comma=%2C/dot=./semi=%3B'
]
}
end
context "Operator ; with value modifiers" do
it_behaves_like 'expands', {
'{;hello:5}' => ';hello=Hello',
'{;list}' => ';list=red,green,blue',
'{;list*}' => ';list=red;list=green;list=blue',
'{;keys}' => [
';keys=semi,%3B,dot,.,comma,%2C',
';keys=dot,.,semi,%3B,comma,%2C',
';keys=comma,%2C,semi,%3B,dot,.',
';keys=semi,%3B,comma,%2C,dot,.',
';keys=dot,.,comma,%2C,semi,%3B',
';keys=comma,%2C,dot,.,semi,%3B'
],
'{;keys*}' => [
';semi=%3B;dot=.;comma=%2C',
';dot=.;semi=%3B;comma=%2C',
';comma=%2C;semi=%3B;dot=.',
';semi=%3B;comma=%2C;dot=.',
';dot=.;comma=%2C;semi=%3B',
';comma=%2C;dot=.;semi=%3B'
]
}
end
context "Operator ? with value modifiers" do
it_behaves_like 'expands', {
'{?var:3}' => '?var=val',
'{?list}' => '?list=red,green,blue',
'{?list*}' => '?list=red&list=green&list=blue',
'{?keys}' => [
'?keys=semi,%3B,dot,.,comma,%2C',
'?keys=dot,.,semi,%3B,comma,%2C',
'?keys=comma,%2C,semi,%3B,dot,.',
'?keys=semi,%3B,comma,%2C,dot,.',
'?keys=dot,.,comma,%2C,semi,%3B',
'?keys=comma,%2C,dot,.,semi,%3B'
],
'{?keys*}' => [
'?semi=%3B&dot=.&comma=%2C',
'?dot=.&semi=%3B&comma=%2C',
'?comma=%2C&semi=%3B&dot=.',
'?semi=%3B&comma=%2C&dot=.',
'?dot=.&comma=%2C&semi=%3B',
'?comma=%2C&dot=.&semi=%3B'
]
}
end
context "Operator & with value modifiers" do
it_behaves_like 'expands', {
'{&var:3}' => '&var=val',
'{&list}' => '&list=red,green,blue',
'{&list*}' => '&list=red&list=green&list=blue',
'{&keys}' => [
'&keys=semi,%3B,dot,.,comma,%2C',
'&keys=dot,.,semi,%3B,comma,%2C',
'&keys=comma,%2C,semi,%3B,dot,.',
'&keys=semi,%3B,comma,%2C,dot,.',
'&keys=dot,.,comma,%2C,semi,%3B',
'&keys=comma,%2C,dot,.,semi,%3B'
],
'{&keys*}' => [
'&semi=%3B&dot=.&comma=%2C',
'&dot=.&semi=%3B&comma=%2C',
'&comma=%2C&semi=%3B&dot=.',
'&semi=%3B&comma=%2C&dot=.',
'&dot=.&comma=%2C&semi=%3B',
'&comma=%2C&dot=.&semi=%3B'
]
}
end
end
describe "Modifiers" do
subject {
{
:var => "value",
:semi => ";",
:year => %w(1965 2000 2012),
:dom => %w(example com)
}
}
context "length" do
it_behaves_like 'expands', {
'{var:3}' => 'val',
'{var:30}' => 'value',
'{var}' => 'value',
'{semi}' => '%3B',
'{semi:2}' => '%3B'
}
end
context "explode" do
it_behaves_like 'expands', {
'find{?year*}' => 'find?year=1965&year=2000&year=2012',
'www{.dom*}' => 'www.example.com',
}
end
end
describe "Expansion" do
subject {
{
: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 => nil
}
}
context "concatenation" do
it_behaves_like 'expands', {
'{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'
}
end
context "simple expansion" do
it_behaves_like 'expands', {
'{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}' => [
'semi,%3B,dot,.,comma,%2C',
'dot,.,semi,%3B,comma,%2C',
'comma,%2C,semi,%3B,dot,.',
'semi,%3B,comma,%2C,dot,.',
'dot,.,comma,%2C,semi,%3B',
'comma,%2C,dot,.,semi,%3B'
],
'{keys*}' => [
'semi=%3B,dot=.,comma=%2C',
'dot=.,semi=%3B,comma=%2C',
'comma=%2C,semi=%3B,dot=.',
'semi=%3B,comma=%2C,dot=.',
'dot=.,comma=%2C,semi=%3B',
'comma=%2C,dot=.,semi=%3B'
]
}
end
context "reserved expansion (+)" do
it_behaves_like 'expands', {
'{+var}' => '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',
'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',
'{+path:6}/here' => '/foo/b/here',
'{+list}' => 'red,green,blue',
'{+list*}' => 'red,green,blue',
'{+keys}' => [
'semi,;,dot,.,comma,,',
'dot,.,semi,;,comma,,',
'comma,,,semi,;,dot,.',
'semi,;,comma,,,dot,.',
'dot,.,comma,,,semi,;',
'comma,,,dot,.,semi,;'
],
'{+keys*}' => [
'semi=;,dot=.,comma=,',
'dot=.,semi=;,comma=,',
'comma=,,semi=;,dot=.',
'semi=;,comma=,,dot=.',
'dot=.,comma=,,semi=;',
'comma=,,dot=.,semi=;'
]
}
end
context "fragment expansion (#)" do
it_behaves_like 'expands', {
'{#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}' => [
'#semi,;,dot,.,comma,,',
'#dot,.,semi,;,comma,,',
'#comma,,,semi,;,dot,.',
'#semi,;,comma,,,dot,.',
'#dot,.,comma,,,semi,;',
'#comma,,,dot,.,semi,;'
],
'{#keys*}' => [
'#semi=;,dot=.,comma=,',
'#dot=.,semi=;,comma=,',
'#comma=,,semi=;,dot=.',
'#semi=;,comma=,,dot=.',
'#dot=.,comma=,,semi=;',
'#comma=,,dot=.,semi=;'
]
}
end
context "label expansion (.)" do
it_behaves_like 'expands', {
'{.who}' => '.fred',
'{.who,who}' => '.fred.fred',
'{.half,who}' => '.50%25.fred',
'www{.dom*}' => 'www.example.com',
'X{.var}' => 'X.value',
'X{.empty}' => 'X.',
'X{.undef}' => 'X',
'X{.var:3}' => 'X.val',
'X{.list}' => 'X.red,green,blue',
'X{.list*}' => 'X.red.green.blue',
'X{.keys}' => [
'X.semi,%3B,dot,.,comma,%2C',
'X.dot,.,semi,%3B,comma,%2C',
'X.comma,%2C,semi,%3B,dot,.',
'X.semi,%3B,comma,%2C,dot,.',
'X.dot,.,comma,%2C,semi,%3B',
'X.comma,%2C,dot,.,semi,%3B'
],
'X{.keys*}' => [
'X.semi=%3B.dot=..comma=%2C',
'X.dot=..semi=%3B.comma=%2C',
'X.comma=%2C.semi=%3B.dot=.',
'X.semi=%3B.comma=%2C.dot=.',
'X.dot=..comma=%2C.semi=%3B',
'X.comma=%2C.dot=..semi=%3B'
],
'X{.empty_keys}' => 'X',
'X{.empty_keys*}' => 'X'
}
end
context "path expansion (/)" do
it_behaves_like 'expands', {
'{/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}' => [
'/semi,%3B,dot,.,comma,%2C',
'/dot,.,semi,%3B,comma,%2C',
'/comma,%2C,semi,%3B,dot,.',
'/semi,%3B,comma,%2C,dot,.',
'/dot,.,comma,%2C,semi,%3B',
'/comma,%2C,dot,.,semi,%3B'
],
'{/keys*}' => [
'/semi=%3B/dot=./comma=%2C',
'/dot=./semi=%3B/comma=%2C',
'/comma=%2C/semi=%3B/dot=.',
'/semi=%3B/comma=%2C/dot=.',
'/dot=./comma=%2C/semi=%3B',
'/comma=%2C/dot=./semi=%3B'
]
}
end
context "path-style expansion (;)" do
it_behaves_like 'expands', {
'{;who}' => ';who=fred',
'{;half}' => ';half=50%25',
'{;empty}' => ';empty',
'{;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',
'{;hello:5}' => ';hello=Hello',
'{;list}' => ';list=red,green,blue',
'{;list*}' => ';list=red;list=green;list=blue',
'{;keys}' => [
';keys=semi,%3B,dot,.,comma,%2C',
';keys=dot,.,semi,%3B,comma,%2C',
';keys=comma,%2C,semi,%3B,dot,.',
';keys=semi,%3B,comma,%2C,dot,.',
';keys=dot,.,comma,%2C,semi,%3B',
';keys=comma,%2C,dot,.,semi,%3B'
],
'{;keys*}' => [
';semi=%3B;dot=.;comma=%2C',
';dot=.;semi=%3B;comma=%2C',
';comma=%2C;semi=%3B;dot=.',
';semi=%3B;comma=%2C;dot=.',
';dot=.;comma=%2C;semi=%3B',
';comma=%2C;dot=.;semi=%3B'
]
}
end
context "form query expansion (?)" do
it_behaves_like 'expands', {
'{?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=semi,%3B,dot,.,comma,%2C',
'?keys=dot,.,semi,%3B,comma,%2C',
'?keys=comma,%2C,semi,%3B,dot,.',
'?keys=semi,%3B,comma,%2C,dot,.',
'?keys=dot,.,comma,%2C,semi,%3B',
'?keys=comma,%2C,dot,.,semi,%3B'
],
'{?keys*}' => [
'?semi=%3B&dot=.&comma=%2C',
'?dot=.&semi=%3B&comma=%2C',
'?comma=%2C&semi=%3B&dot=.',
'?semi=%3B&comma=%2C&dot=.',
'?dot=.&comma=%2C&semi=%3B',
'?comma=%2C&dot=.&semi=%3B'
]
}
end
context "form query expansion (&)" do
it_behaves_like 'expands', {
'{&who}' => '&who=fred',
'{&half}' => '&half=50%25',
'?fixed=yes{&x}' => '?fixed=yes&x=1024',
'{&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=semi,%3B,dot,.,comma,%2C',
'&keys=dot,.,semi,%3B,comma,%2C',
'&keys=comma,%2C,semi,%3B,dot,.',
'&keys=semi,%3B,comma,%2C,dot,.',
'&keys=dot,.,comma,%2C,semi,%3B',
'&keys=comma,%2C,dot,.,semi,%3B'
],
'{&keys*}' => [
'&semi=%3B&dot=.&comma=%2C',
'&dot=.&semi=%3B&comma=%2C',
'&comma=%2C&semi=%3B&dot=.',
'&semi=%3B&comma=%2C&dot=.',
'&dot=.&comma=%2C&semi=%3B',
'&comma=%2C&dot=.&semi=%3B'
]
}
end
context "non-string key in match data" do
subject {Addressable::Template.new("http://example.com/{one}")}
it "raises TypeError" do
expect { subject.expand(Object.new => "1") }.to raise_error TypeError
end
end
end
class ExampleTwoProcessor
def self.restore(name, value)
return value.gsub(/-/, " ") if name == "query"
return value
end
def self.match(name)
return ".*?" if name == "first"
return ".*"
end
def self.validate(name, value)
return !!(value =~ /^[\w ]+$/) if name == "query"
return true
end
def self.transform(name, value)
return value.gsub(/ /, "+") if name == "query"
return value
end
end
class DumbProcessor
def self.match(name)
return ".*?" if name == "first"
end
end
describe Addressable::Template do
describe 'initialize' do
context 'with a non-string' do
it 'raises a TypeError' do
expect { Addressable::Template.new(nil) }.to raise_error(TypeError)
end
end
end
describe 'freeze' do
subject { Addressable::Template.new("http://example.com/{first}/{+second}/") }
it 'freezes the template' do
expect(subject.freeze).to be_frozen
end
end
describe "Matching" do
let(:uri){
Addressable::URI.parse(
"http://example.com/search/an-example-search-query/"
)
}
let(:uri2){
Addressable::URI.parse("http://example.com/a/b/c/")
}
let(:uri3){
Addressable::URI.parse("http://example.com/;a=1;b=2;c=3;first=foo")
}
let(:uri4){
Addressable::URI.parse("http://example.com/?a=1&b=2&c=3&first=foo")
}
let(:uri5){
"http://example.com/foo"
}
context "first uri with ExampleTwoProcessor" do
subject {
Addressable::Template.new(
"http://example.com/search/{query}/"
).match(uri, ExampleTwoProcessor)
}
its(:variables){ should == ["query"] }
its(:captures){ should == ["an example search query"] }
end
context "second uri with ExampleTwoProcessor" do
subject {
Addressable::Template.new(
"http://example.com/{first}/{+second}/"
).match(uri2, ExampleTwoProcessor)
}
its(:variables){ should == ["first", "second"] }
its(:captures){ should == ["a", "b/c"] }
end
context "second uri with DumbProcessor" do
subject {
Addressable::Template.new(
"http://example.com/{first}/{+second}/"
).match(uri2, DumbProcessor)
}
its(:variables){ should == ["first", "second"] }
its(:captures){ should == ["a", "b/c"] }
end
context "second uri" do
subject {
Addressable::Template.new(
"http://example.com/{first}{/second*}/"
).match(uri2)
}
its(:variables){ should == ["first", "second"] }
its(:captures){ should == ["a", ["b","c"]] }
end
context "third uri" do
subject {
Addressable::Template.new(
"http://example.com/{;hash*,first}"
).match(uri3)
}
its(:variables){ should == ["hash", "first"] }
its(:captures){ should == [
{"a" => "1", "b" => "2", "c" => "3", "first" => "foo"}, nil] }
end
# Note that this expansion is impossible to revert deterministically - the
# * operator means first could have been a key of hash or a separate key.
# Semantically, a separate key is more likely, but both are possible.
context "fourth uri" do
subject {
Addressable::Template.new(
"http://example.com/{?hash*,first}"
).match(uri4)
}
its(:variables){ should == ["hash", "first"] }
its(:captures){ should == [
{"a" => "1", "b" => "2", "c" => "3", "first"=> "foo"}, nil] }
end
context "fifth uri" do
subject {
Addressable::Template.new(
"http://example.com/{path}{?hash*,first}"
).match(uri5)
}
its(:variables){ should == ["path", "hash", "first"] }
its(:captures){ should == ["foo", nil, nil] }
end
end
describe 'match' do
subject { Addressable::Template.new('http://example.com/first/second/') }
context 'when the URI is the same as the template' do
it 'returns the match data itself with an empty mapping' do
uri = Addressable::URI.parse('http://example.com/first/second/')
match_data = subject.match(uri)
expect(match_data).to be_an Addressable::Template::MatchData
expect(match_data.uri).to eq(uri)
expect(match_data.template).to eq(subject)
expect(match_data.mapping).to be_empty
expect(match_data.inspect).to be_an String
end
end
end
describe "extract" do
let(:template) {
Addressable::Template.new(
"http://{host}{/segments*}/{?one,two,bogus}{#fragment}"
)
}
let(:uri){ "http://example.com/a/b/c/?one=1&two=2#foo" }
let(:uri2){ "http://example.com/a/b/c/#foo" }
it "should be able to extract with queries" do
expect(template.extract(uri)).to eq({
"host" => "example.com",
"segments" => %w(a b c),
"one" => "1",
"bogus" => nil,
"two" => "2",
"fragment" => "foo"
})
end
it "should be able to extract without queries" do
expect(template.extract(uri2)).to eq({
"host" => "example.com",
"segments" => %w(a b c),
"one" => nil,
"bogus" => nil,
"two" => nil,
"fragment" => "foo"
})
end
context "issue #137" do
subject { Addressable::Template.new('/path{?page,per_page}') }
it "can match empty" do
data = subject.extract("/path")
expect(data["page"]).to eq(nil)
expect(data["per_page"]).to eq(nil)
expect(data.keys.sort).to eq(['page', 'per_page'])
end
it "can match first var" do
data = subject.extract("/path?page=1")
expect(data["page"]).to eq("1")
expect(data["per_page"]).to eq(nil)
expect(data.keys.sort).to eq(['page', 'per_page'])
end
it "can match second var" do
data = subject.extract("/path?per_page=1")
expect(data["page"]).to eq(nil)
expect(data["per_page"]).to eq("1")
expect(data.keys.sort).to eq(['page', 'per_page'])
end
it "can match both vars" do
data = subject.extract("/path?page=2&per_page=1")
expect(data["page"]).to eq("2")
expect(data["per_page"]).to eq("1")
expect(data.keys.sort).to eq(['page', 'per_page'])
end
end
end
describe "Partial expand with symbols" do
context "partial_expand with two simple values" do
subject {
Addressable::Template.new("http://example.com/{one}/{two}/")
}
it "builds a new pattern" do
expect(subject.partial_expand(:one => "1").pattern).to eq(
"http://example.com/1/{two}/"
)
end
end
context "partial_expand query with missing param in middle" do
subject {
Addressable::Template.new("http://example.com/{?one,two,three}/")
}
it "builds a new pattern" do
expect(subject.partial_expand(:one => "1", :three => "3").pattern).to eq(
"http://example.com/?one=1{&two}&three=3/"
)
end
end
context "partial_expand form style query with missing param at beginning" do
subject {
Addressable::Template.new("http://example.com/{?one,two}/")
}
it "builds a new pattern" do
expect(subject.partial_expand(:two => "2").pattern).to eq(
"http://example.com/?two=2{&one}/"
)
end
end
context "partial_expand with query string" do
subject {
Addressable::Template.new("http://example.com/{?two,one}/")
}
it "builds a new pattern" do
expect(subject.partial_expand(:one => "1").pattern).to eq(
"http://example.com/?one=1{&two}/"
)
end
end
context "partial_expand with path operator" do
subject {
Addressable::Template.new("http://example.com{/one,two}/")
}
it "builds a new pattern" do
expect(subject.partial_expand(:one => "1").pattern).to eq(
"http://example.com/1{/two}/"
)
end
end
context "partial expand with unicode values" do
subject do
Addressable::Template.new("http://example.com/{resource}/{query}/")
end
it "normalizes unicode by default" do
template = subject.partial_expand("query" => "Cafe\u0301")
expect(template.pattern).to eq(
"http://example.com/{resource}/Caf%C3%A9/"
)
end
it "does not normalize unicode when byte semantics requested" do
template = subject.partial_expand({"query" => "Cafe\u0301"}, nil, false)
expect(template.pattern).to eq(
"http://example.com/{resource}/Cafe%CC%81/"
)
end
end
end
describe "Partial expand with strings" do
context "partial_expand with two simple values" do
subject {
Addressable::Template.new("http://example.com/{one}/{two}/")
}
it "builds a new pattern" do
expect(subject.partial_expand("one" => "1").pattern).to eq(
"http://example.com/1/{two}/"
)
end
end
context "partial_expand query with missing param in middle" do
subject {
Addressable::Template.new("http://example.com/{?one,two,three}/")
}
it "builds a new pattern" do
expect(subject.partial_expand("one" => "1", "three" => "3").pattern).to eq(
"http://example.com/?one=1{&two}&three=3/"
)
end
end
context "partial_expand with query string" do
subject {
Addressable::Template.new("http://example.com/{?two,one}/")
}
it "builds a new pattern" do
expect(subject.partial_expand("one" => "1").pattern).to eq(
"http://example.com/?one=1{&two}/"
)
end
end
context "partial_expand with path operator" do
subject {
Addressable::Template.new("http://example.com{/one,two}/")
}
it "builds a new pattern" do
expect(subject.partial_expand("one" => "1").pattern).to eq(
"http://example.com/1{/two}/"
)
end
end
end
describe "Expand" do
context "expand with unicode values" do
subject do
Addressable::Template.new("http://example.com/search/{query}/")
end
it "normalizes unicode by default" do
uri = subject.expand("query" => "Cafe\u0301").to_str
expect(uri).to eq("http://example.com/search/Caf%C3%A9/")
end
it "does not normalize unicode when byte semantics requested" do
uri = subject.expand({ "query" => "Cafe\u0301" }, nil, false).to_str
expect(uri).to eq("http://example.com/search/Cafe%CC%81/")
end
end
context "expand with a processor" do
subject {
Addressable::Template.new("http://example.com/search/{query}/")
}
it "processes spaces" do
expect(subject.expand({"query" => "an example search query"},
ExampleTwoProcessor).to_str).to eq(
"http://example.com/search/an+example+search+query/"
)
end
it "validates" do
expect{
subject.expand({"query" => "Bogus!"},
ExampleTwoProcessor).to_str
}.to raise_error(Addressable::Template::InvalidTemplateValueError)
end
end
context "partial_expand query with missing param in middle" do
subject {
Addressable::Template.new("http://example.com/{?one,two,three}/")
}
it "builds a new pattern" do
expect(subject.partial_expand("one" => "1", "three" => "3").pattern).to eq(
"http://example.com/?one=1{&two}&three=3/"
)
end
end
context "partial_expand with query string" do
subject {
Addressable::Template.new("http://example.com/{?two,one}/")
}
it "builds a new pattern" do
expect(subject.partial_expand("one" => "1").pattern).to eq(
"http://example.com/?one=1{&two}/"
)
end
end
context "partial_expand with path operator" do
subject {
Addressable::Template.new("http://example.com{/one,two}/")
}
it "builds a new pattern" do
expect(subject.partial_expand("one" => "1").pattern).to eq(
"http://example.com/1{/two}/"
)
end
end
end
context "Matching with operators" do
describe "Level 1:" do
subject { Addressable::Template.new("foo{foo}/{bar}baz") }
it "can match" do
data = subject.match("foofoo/bananabaz")
expect(data.mapping["foo"]).to eq("foo")
expect(data.mapping["bar"]).to eq("banana")
end
it "can fail" do
expect(subject.match("bar/foo")).to be_nil
expect(subject.match("foobaz")).to be_nil
end
it "can match empty" do
data = subject.match("foo/baz")
expect(data.mapping["foo"]).to eq(nil)
expect(data.mapping["bar"]).to eq(nil)
end
it "lists vars" do
expect(subject.variables).to eq(["foo", "bar"])
end
end
describe "Level 2:" do
subject { Addressable::Template.new("foo{+foo}{#bar}baz") }
it "can match" do
data = subject.match("foo/test/banana#bazbaz")
expect(data.mapping["foo"]).to eq("/test/banana")
expect(data.mapping["bar"]).to eq("baz")
end
it "can match empty level 2 #" do
data = subject.match("foo/test/bananabaz")
expect(data.mapping["foo"]).to eq("/test/banana")
expect(data.mapping["bar"]).to eq(nil)
data = subject.match("foo/test/banana#baz")
expect(data.mapping["foo"]).to eq("/test/banana")
expect(data.mapping["bar"]).to eq("")
end
it "can match empty level 2 +" do
data = subject.match("foobaz")
expect(data.mapping["foo"]).to eq(nil)
expect(data.mapping["bar"]).to eq(nil)
data = subject.match("foo#barbaz")
expect(data.mapping["foo"]).to eq(nil)
expect(data.mapping["bar"]).to eq("bar")
end
it "lists vars" do
expect(subject.variables).to eq(["foo", "bar"])
end
end
describe "Level 3:" do
context "no operator" do
subject { Addressable::Template.new("foo{foo,bar}baz") }
it "can match" do
data = subject.match("foofoo,barbaz")
expect(data.mapping["foo"]).to eq("foo")
expect(data.mapping["bar"]).to eq("bar")
end
it "lists vars" do
expect(subject.variables).to eq(["foo", "bar"])
end
end
context "+ operator" do
subject { Addressable::Template.new("foo{+foo,bar}baz") }
it "can match" do
data = subject.match("foofoo/bar,barbaz")
expect(data.mapping["bar"]).to eq("foo/bar,bar")
expect(data.mapping["foo"]).to eq("")
end
it "lists vars" do
expect(subject.variables).to eq(["foo", "bar"])
end
end
context ". operator" do
subject { Addressable::Template.new("foo{.foo,bar}baz") }
it "can match" do
data = subject.match("foo.foo.barbaz")
expect(data.mapping["foo"]).to eq("foo")
expect(data.mapping["bar"]).to eq("bar")
end
it "lists vars" do
expect(subject.variables).to eq(["foo", "bar"])
end
end
context "/ operator" do
subject { Addressable::Template.new("foo{/foo,bar}baz") }
it "can match" do
data = subject.match("foo/foo/barbaz")
expect(data.mapping["foo"]).to eq("foo")
expect(data.mapping["bar"]).to eq("bar")
end
it "lists vars" do
expect(subject.variables).to eq(["foo", "bar"])
end
end
context "; operator" do
subject { Addressable::Template.new("foo{;foo,bar,baz}baz") }
it "can match" do
data = subject.match("foo;foo=bar%20baz;bar=foo;bazbaz")
expect(data.mapping["foo"]).to eq("bar baz")
expect(data.mapping["bar"]).to eq("foo")
expect(data.mapping["baz"]).to eq("")
end
it "lists vars" do
expect(subject.variables).to eq(%w(foo bar baz))
end
end
context "? operator" do
context "test" do
subject { Addressable::Template.new("foo{?foo,bar}baz") }
it "can match" do
data = subject.match("foo?foo=bar%20baz&bar=foobaz")
expect(data.mapping["foo"]).to eq("bar baz")
expect(data.mapping["bar"]).to eq("foo")
end
it "lists vars" do
expect(subject.variables).to eq(%w(foo bar))
end
end
context "issue #137" do
subject { Addressable::Template.new('/path{?page,per_page}') }
it "can match empty" do
data = subject.match("/path")
expect(data.mapping["page"]).to eq(nil)
expect(data.mapping["per_page"]).to eq(nil)
expect(data.mapping.keys.sort).to eq(['page', 'per_page'])
end
it "can match first var" do
data = subject.match("/path?page=1")
expect(data.mapping["page"]).to eq("1")
expect(data.mapping["per_page"]).to eq(nil)
expect(data.mapping.keys.sort).to eq(['page', 'per_page'])
end
it "can match second var" do
data = subject.match("/path?per_page=1")
expect(data.mapping["page"]).to eq(nil)
expect(data.mapping["per_page"]).to eq("1")
expect(data.mapping.keys.sort).to eq(['page', 'per_page'])
end
it "can match both vars" do
data = subject.match("/path?page=2&per_page=1")
expect(data.mapping["page"]).to eq("2")
expect(data.mapping["per_page"]).to eq("1")
expect(data.mapping.keys.sort).to eq(['page', 'per_page'])
end
end
context "issue #71" do
subject { Addressable::Template.new("http://cyberscore.dev/api/users{?username}") }
it "can match" do
data = subject.match("http://cyberscore.dev/api/users?username=foobaz")
expect(data.mapping["username"]).to eq("foobaz")
end
it "lists vars" do
expect(subject.variables).to eq(%w(username))
expect(subject.keys).to eq(%w(username))
end
end
end
context "& operator" do
subject { Addressable::Template.new("foo{&foo,bar}baz") }
it "can match" do
data = subject.match("foo&foo=bar%20baz&bar=foobaz")
expect(data.mapping["foo"]).to eq("bar baz")
expect(data.mapping["bar"]).to eq("foo")
end
it "lists vars" do
expect(subject.variables).to eq(%w(foo bar))
end
end
end
end
context "support regexes:" do
context "EXPRESSION" do
subject { Addressable::Template::EXPRESSION }
it "should be able to match an expression" do
expect(subject).to match("{foo}")
expect(subject).to match("{foo,9}")
expect(subject).to match("{foo.bar,baz}")
expect(subject).to match("{+foo.bar,baz}")
expect(subject).to match("{foo,foo%20bar}")
expect(subject).to match("{#foo:20,baz*}")
expect(subject).to match("stuff{#foo:20,baz*}things")
end
it "should fail on non vars" do
expect(subject).not_to match("!{foo")
expect(subject).not_to match("{foo.bar.}")
expect(subject).not_to match("!{}")
end
end
context "VARNAME" do
subject { Addressable::Template::VARNAME }
it "should be able to match a variable" do
expect(subject).to match("foo")
expect(subject).to match("9")
expect(subject).to match("foo.bar")
expect(subject).to match("foo_bar")
expect(subject).to match("foo_bar.baz")
expect(subject).to match("foo%20bar")
expect(subject).to match("foo%20bar.baz")
end
it "should fail on non vars" do
expect(subject).not_to match("!foo")
expect(subject).not_to match("foo.bar.")
expect(subject).not_to match("foo%2%00bar")
expect(subject).not_to match("foo_ba%r")
expect(subject).not_to match("foo_bar*")
expect(subject).not_to match("foo_bar:20")
end
end
context "VARIABLE_LIST" do
subject { Addressable::Template::VARIABLE_LIST }
it "should be able to match a variable list" do
expect(subject).to match("foo,bar")
expect(subject).to match("foo")
expect(subject).to match("foo,bar*,baz")
expect(subject).to match("foo.bar,bar_baz*,baz:12")
end
it "should fail on non vars" do
expect(subject).not_to match(",foo,bar*,baz")
expect(subject).not_to match("foo,*bar,baz")
expect(subject).not_to match("foo,,bar*,baz")
end
end
context "VARSPEC" do
subject { Addressable::Template::VARSPEC }
it "should be able to match a variable with modifier" do
expect(subject).to match("9:8")
expect(subject).to match("foo.bar*")
expect(subject).to match("foo_bar:12")
expect(subject).to match("foo_bar.baz*")
expect(subject).to match("foo%20bar:12")
expect(subject).to match("foo%20bar.baz*")
end
it "should fail on non vars" do
expect(subject).not_to match("!foo")
expect(subject).not_to match("*foo")
expect(subject).not_to match("fo*o")
expect(subject).not_to match("fo:o")
expect(subject).not_to match("foo:")
end
end
end
end
describe Addressable::Template::MatchData do
let(:template) { Addressable::Template.new('{foo}/{bar}') }
subject(:its) { template.match('ab/cd') }
its(:uri) { should == Addressable::URI.parse('ab/cd') }
its(:template) { should == template }
its(:mapping) { should == { 'foo' => 'ab', 'bar' => 'cd' } }
its(:variables) { should == ['foo', 'bar'] }
its(:keys) { should == ['foo', 'bar'] }
its(:names) { should == ['foo', 'bar'] }
its(:values) { should == ['ab', 'cd'] }
its(:captures) { should == ['ab', 'cd'] }
its(:to_a) { should == ['ab/cd', 'ab', 'cd'] }
its(:to_s) { should == 'ab/cd' }
its(:string) { should == its.to_s }
its(:pre_match) { should == "" }
its(:post_match) { should == "" }
describe 'values_at' do
it 'returns an array with the values' do
expect(its.values_at(0, 2)).to eq(['ab/cd', 'cd'])
end
it 'allows mixing integer an string keys' do
expect(its.values_at('foo', 1)).to eq(['ab', 'ab'])
end
it 'accepts unknown keys' do
expect(its.values_at('baz', 'foo')).to eq([nil, 'ab'])
end
end
describe '[]' do
context 'string key' do
it 'returns the corresponding capture' do
expect(its['foo']).to eq('ab')
expect(its['bar']).to eq('cd')
end
it 'returns nil for unknown keys' do
expect(its['baz']).to be_nil
end
end
context 'symbol key' do
it 'returns the corresponding capture' do
expect(its[:foo]).to eq('ab')
expect(its[:bar]).to eq('cd')
end
it 'returns nil for unknown keys' do
expect(its[:baz]).to be_nil
end
end
context 'integer key' do
it 'returns the full URI for index 0' do
expect(its[0]).to eq('ab/cd')
end
it 'returns the corresponding capture' do
expect(its[1]).to eq('ab')
expect(its[2]).to eq('cd')
end
it 'returns nil for unknown keys' do
expect(its[3]).to be_nil
end
end
context 'other key' do
it 'raises an exception' do
expect { its[Object.new] }.to raise_error(TypeError)
end
end
context 'with length' do
it 'returns an array starting at index with given length' do
expect(its[0, 2]).to eq(['ab/cd', 'ab'])
expect(its[2, 1]).to eq(['cd'])
end
end
end
end