dav4rack_ext 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -1
- data/Rakefile +2 -1
- data/lib/dav4rack_ext/carddav/resources/contact_resource.rb +19 -1
- data/lib/dav4rack_ext/version.rb +1 -1
- data/specs/helpers/http_dav.rb +63 -0
- data/specs/spec_helper.rb +1 -10
- data/specs/support/models.rb +4 -0
- data/specs/unit/carddav/resources/contact_resource_spec.rb +96 -0
- metadata +6 -4
data/Gemfile
CHANGED
data/Rakefile
CHANGED
@@ -85,6 +85,21 @@ module DAV4Rack
|
|
85
85
|
# If the client has explicitly stated they want a new contact
|
86
86
|
raise Conflict if (want_new_contact and @contact)
|
87
87
|
|
88
|
+
if_match = request.env['HTTP_IF_MATCH']
|
89
|
+
if if_match
|
90
|
+
# client wants to update a contact, return an error if no
|
91
|
+
# contact was found
|
92
|
+
if (if_match == '*') || !@contact
|
93
|
+
raise PreconditionFailed unless @contact
|
94
|
+
|
95
|
+
# client wants to update the contact with specific etag,
|
96
|
+
# return an error if the contact was updated by someone else
|
97
|
+
elsif (if_match != @contact.etag)
|
98
|
+
raise PreconditionFailed
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
88
103
|
if @contact
|
89
104
|
Logger.debug("Updating contact #{uid} (#{@contact.object_id})")
|
90
105
|
else
|
@@ -95,7 +110,10 @@ module DAV4Rack
|
|
95
110
|
@contact.update_from_vcard(vcf)
|
96
111
|
|
97
112
|
if @contact.save
|
98
|
-
|
113
|
+
new_public = @public_path.split('/')[0...-1]
|
114
|
+
new_public << @contact.uid.to_s
|
115
|
+
|
116
|
+
@public_path = new_public.join('/')
|
99
117
|
response['ETag'] = @contact.etag
|
100
118
|
Created
|
101
119
|
else
|
data/lib/dav4rack_ext/version.rb
CHANGED
@@ -0,0 +1,63 @@
|
|
1
|
+
module HTTPDAVTest
|
2
|
+
def propfind(url, properties = :all, opts = {})
|
3
|
+
namespaces = {
|
4
|
+
'DAV:' => 'D',
|
5
|
+
'urn:ietf:params:xml:ns:carddav' => 'C',
|
6
|
+
'http://calendarserver.org/ns/' => 'APPLE1'
|
7
|
+
}
|
8
|
+
|
9
|
+
if properties == :all
|
10
|
+
body = "<D:allprop />"
|
11
|
+
|
12
|
+
else
|
13
|
+
properties = properties.map do |(name, ns)|
|
14
|
+
ns_short = namespaces[ns]
|
15
|
+
raise "unknown namespace: #{ns}" unless ns_short
|
16
|
+
%.<#{ns_short}:#{name}/>.
|
17
|
+
end
|
18
|
+
|
19
|
+
body = "<D:prop>#{properties.join("\n")}</D:prop>"
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
data = <<-EOS
|
24
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
25
|
+
<D:propfind xmlns:D="DAV:" xmlns:C="urn:ietf:params:xml:ns:carddav" xmlns:APPLE1="http://calendarserver.org/ns/">
|
26
|
+
#{body}
|
27
|
+
</D:propfind>
|
28
|
+
EOS
|
29
|
+
|
30
|
+
request('PROPFIND', url, opts.merge(input: data))
|
31
|
+
end
|
32
|
+
|
33
|
+
def ensure_element_exists(response, expr, namespaces = {'D' => 'DAV:'})
|
34
|
+
ret = Nokogiri::XML(response.body)
|
35
|
+
ret.css(expr, namespaces).tap{|elements| elements.should.not.be.empty? }
|
36
|
+
rescue EEtee::AssertionFailed => err
|
37
|
+
raise EEtee::AssertionFailed.new("XML did not match: #{expr}")
|
38
|
+
end
|
39
|
+
|
40
|
+
def ensure_element_does_not_exists(response, expr, namespaces = {})
|
41
|
+
ret = Nokogiri::XML(response.body)
|
42
|
+
ret.css(expr, namespaces).should.be.empty?
|
43
|
+
rescue EEtee::AssertionFailed => err
|
44
|
+
raise EEtee::AssertionFailed.new("XML did match: #{expr}")
|
45
|
+
end
|
46
|
+
|
47
|
+
def element_content(response, expr, namespaces = {})
|
48
|
+
ret = Nokogiri::XML(response.body)
|
49
|
+
elements = ret.css(expr, namespaces)
|
50
|
+
if elements.empty?
|
51
|
+
:missing
|
52
|
+
else
|
53
|
+
children = elements.first.element_children
|
54
|
+
if children.empty?
|
55
|
+
:empty
|
56
|
+
else
|
57
|
+
children.first.text
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
EEtee::Context.__send__(:include, HTTPDAVTest)
|
data/specs/spec_helper.rb
CHANGED
@@ -3,16 +3,6 @@ require 'bundler/setup'
|
|
3
3
|
|
4
4
|
require 'eetee'
|
5
5
|
|
6
|
-
if ENV['COVERAGE']
|
7
|
-
|
8
|
-
require 'simplecov'
|
9
|
-
SimpleCov.start do
|
10
|
-
add_filter ".*_spec"
|
11
|
-
add_filter "/helpers/"
|
12
|
-
end
|
13
|
-
|
14
|
-
end
|
15
|
-
|
16
6
|
$LOAD_PATH.unshift( File.expand_path('../../lib' , __FILE__) )
|
17
7
|
require 'dav4rack_ext'
|
18
8
|
require 'factory_girl'
|
@@ -21,6 +11,7 @@ require 'factory_girl'
|
|
21
11
|
require 'eetee/ext/mocha'
|
22
12
|
require 'eetee/ext/rack'
|
23
13
|
|
14
|
+
require_relative 'helpers/http_dav'
|
24
15
|
|
25
16
|
require_relative '../example/rack_sniffer'
|
26
17
|
require_relative 'support/models'
|
data/specs/support/models.rb
CHANGED
@@ -0,0 +1,96 @@
|
|
1
|
+
require File.expand_path('../../../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe 'Contact Resource' do
|
4
|
+
before do
|
5
|
+
@dav_ns = "DAV:"
|
6
|
+
@carddav_ns = "urn:ietf:params:xml:ns:carddav"
|
7
|
+
|
8
|
+
@contact = contact = FactoryGirl.build(:contact, uid: '1234-5678-9000-1')
|
9
|
+
|
10
|
+
|
11
|
+
user_builder = proc do |env|
|
12
|
+
FactoryGirl.build(:user, env: env, login: 'john', addressbooks: [
|
13
|
+
FactoryGirl.build(:book, path: 'castor', name: "A book", contacts: [contact])
|
14
|
+
])
|
15
|
+
end
|
16
|
+
|
17
|
+
app = Rack::Builder.new do
|
18
|
+
# use XMLSniffer
|
19
|
+
run DAV4Rack::Carddav.app('/', current_user: user_builder)
|
20
|
+
end
|
21
|
+
|
22
|
+
serve_app(app)
|
23
|
+
end
|
24
|
+
|
25
|
+
should 'update contact and return correct location', :force => true do
|
26
|
+
# the url does not need to match the UID
|
27
|
+
response = request(:put, '/books/castor/crap',
|
28
|
+
input: @contact.vcard.to_s
|
29
|
+
)
|
30
|
+
|
31
|
+
response.status.should == 201
|
32
|
+
response.headers['Location'].should == 'http://example.org:80/books/castor/1234-5678-9000-1'
|
33
|
+
end
|
34
|
+
|
35
|
+
should 'return an error if If-Match do not match (rfc2068 14.25)' do
|
36
|
+
Testing::Contact.any_instance.expects(:etag).returns("ETAG")
|
37
|
+
|
38
|
+
headers = {
|
39
|
+
'HTTP_IF_MATCH' => 'ETAG2'
|
40
|
+
}
|
41
|
+
|
42
|
+
# the url does not need to match the UID
|
43
|
+
response = request(:put, '/books/castor/1234-5678-9000-1',
|
44
|
+
headers.merge(input: @contact.vcard.to_s)
|
45
|
+
)
|
46
|
+
|
47
|
+
response.status.should == 412
|
48
|
+
end
|
49
|
+
|
50
|
+
should 'return an error with If-Match and no contact (rfc2068 14.25)' do
|
51
|
+
headers = {
|
52
|
+
'HTTP_IF_MATCH' => 'ETAG2'
|
53
|
+
}
|
54
|
+
|
55
|
+
c = @contact.dup
|
56
|
+
c.uid = '55TT'
|
57
|
+
|
58
|
+
# the url does not need to match the UID
|
59
|
+
response = request(:put, '/books/castor/CRAP',
|
60
|
+
headers.merge(input: c.vcard.to_s)
|
61
|
+
)
|
62
|
+
|
63
|
+
response.status.should == 412
|
64
|
+
end
|
65
|
+
|
66
|
+
|
67
|
+
should 'update contact if If-Match="*" and contact was found (rfc2068 14.25)' do
|
68
|
+
headers = {
|
69
|
+
'HTTP_IF_MATCH' => '*'
|
70
|
+
}
|
71
|
+
|
72
|
+
# the url does not need to match the UID
|
73
|
+
response = request(:put, '/books/castor/1234-5678-9000-1',
|
74
|
+
headers.merge(input: @contact.vcard.to_s)
|
75
|
+
)
|
76
|
+
|
77
|
+
response.status.should == 201
|
78
|
+
end
|
79
|
+
|
80
|
+
should 'not update contact if If-Match="*" and no contact found (rfc2068 14.25)' do
|
81
|
+
headers = {
|
82
|
+
'HTTP_IF_MATCH' => '*'
|
83
|
+
}
|
84
|
+
|
85
|
+
c = @contact.dup
|
86
|
+
c.uid = '55TT'
|
87
|
+
|
88
|
+
# the url does not need to match the UID
|
89
|
+
response = request(:put, '/books/castor/undefined',
|
90
|
+
headers.merge(input: c.vcard.to_s)
|
91
|
+
)
|
92
|
+
|
93
|
+
response.status.should == 412
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dav4rack_ext
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-12-
|
12
|
+
date: 2012-12-09 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: dav4rack
|
@@ -91,6 +91,7 @@ files:
|
|
91
91
|
- lib/dav4rack_ext/helpers/properties.rb
|
92
92
|
- lib/dav4rack_ext/version.rb
|
93
93
|
- specs/factories.rb
|
94
|
+
- specs/helpers/http_dav.rb
|
94
95
|
- specs/rfc/rfc3744_spec.rb
|
95
96
|
- specs/rfc/rfc5397_spec.rb
|
96
97
|
- specs/rfc/rfc6352_spec.rb
|
@@ -98,6 +99,7 @@ files:
|
|
98
99
|
- specs/support/models.rb
|
99
100
|
- specs/unit/carddav/app_spec.rb
|
100
101
|
- specs/unit/carddav/resources/addressbook_collection_resource_spec.rb
|
102
|
+
- specs/unit/carddav/resources/contact_resource_spec.rb
|
101
103
|
- specs/unit/carddav/resources/principal_resource_spec.rb
|
102
104
|
- specs/unit/helpers/properties_spec.rb
|
103
105
|
homepage: ''
|
@@ -114,7 +116,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
114
116
|
version: '0'
|
115
117
|
segments:
|
116
118
|
- 0
|
117
|
-
hash:
|
119
|
+
hash: 2833907520245342566
|
118
120
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
119
121
|
none: false
|
120
122
|
requirements:
|
@@ -123,7 +125,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
123
125
|
version: '0'
|
124
126
|
segments:
|
125
127
|
- 0
|
126
|
-
hash:
|
128
|
+
hash: 2833907520245342566
|
127
129
|
requirements: []
|
128
130
|
rubyforge_project:
|
129
131
|
rubygems_version: 1.8.23
|