dav4rack_ext 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|