1452 lines
45 KiB
Ruby
1452 lines
45 KiB
Ruby
|
# frozen_string_literal: true
|
||
|
|
||
|
# 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('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 "issue #307 - partial_expand form query with nil params" do
|
||
|
subject do
|
||
|
Addressable::Template.new("http://example.com/{?one,two,three}/")
|
||
|
end
|
||
|
it "builds a new pattern with two=nil" do
|
||
|
expect(subject.partial_expand(two: nil).pattern).to eq(
|
||
|
"http://example.com/{?one}{&three}/"
|
||
|
)
|
||
|
end
|
||
|
it "builds a new pattern with one=nil and two=nil" do
|
||
|
expect(subject.partial_expand(one: nil, two: nil).pattern).to eq(
|
||
|
"http://example.com/{?three}/"
|
||
|
)
|
||
|
end
|
||
|
it "builds a new pattern with one=1 and two=nil" do
|
||
|
expect(subject.partial_expand(one: 1, two: nil).pattern).to eq(
|
||
|
"http://example.com/?one=1{&three}/"
|
||
|
)
|
||
|
end
|
||
|
it "builds a new pattern with one=nil and two=2" do
|
||
|
expect(subject.partial_expand(one: nil, two: 2).pattern).to eq(
|
||
|
"http://example.com/?two=2{&three}/"
|
||
|
)
|
||
|
end
|
||
|
it "builds a new pattern with one=nil" do
|
||
|
expect(subject.partial_expand(one: nil).pattern).to eq(
|
||
|
"http://example.com/{?two}{&three}/"
|
||
|
)
|
||
|
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
|