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 CHANGED
@@ -13,7 +13,7 @@ group(:example) do
13
13
  end
14
14
 
15
15
  group(:test) do
16
- gem 'eetee', '~> 0.0.1'
16
+ gem 'eetee', '~> 0.0.3'
17
17
  gem 'mocha', '~> 0.12.0'
18
18
  gem 'factory_girl', '~> 4.0'
19
19
  gem 'virtus'
data/Rakefile CHANGED
@@ -13,7 +13,8 @@ task :test do
13
13
  SimpleCov.command_name "E.T."
14
14
  SimpleCov.start do
15
15
  add_filter ".*_spec"
16
- add_filter "/helpers/"
16
+ add_filter "/specs/helpers/"
17
+ add_filter "/example/"
17
18
  end
18
19
  end
19
20
 
@@ -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
- @public_path = File.join(@address_book.path, @contact.uid)
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
@@ -1,3 +1,3 @@
1
1
  module Dav4rackExt
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.5"
3
3
  end
@@ -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'
@@ -93,6 +93,10 @@ module Testing
93
93
  contacts.detect{|c| c.uid == uid.to_s }
94
94
  end
95
95
 
96
+ def ctag
97
+ updated_at
98
+ end
99
+
96
100
  def create_contact(uid)
97
101
  Contact.new(uid: uid).tap do |c|
98
102
  contacts << c
@@ -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
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-06 00:00:00.000000000 Z
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: 1991080700414274761
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: 1991080700414274761
128
+ hash: 2833907520245342566
127
129
  requirements: []
128
130
  rubyforge_project:
129
131
  rubygems_version: 1.8.23