diaspora-vines 0.1.2

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.
Files changed (174) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -0
  3. data/LICENSE +19 -0
  4. data/README.md +7 -0
  5. data/Rakefile +23 -0
  6. data/bin/vines +4 -0
  7. data/conf/certs/README +39 -0
  8. data/conf/certs/ca-bundle.crt +3895 -0
  9. data/conf/config.rb +42 -0
  10. data/lib/vines/cli.rb +132 -0
  11. data/lib/vines/cluster/connection.rb +26 -0
  12. data/lib/vines/cluster/publisher.rb +55 -0
  13. data/lib/vines/cluster/pubsub.rb +92 -0
  14. data/lib/vines/cluster/sessions.rb +125 -0
  15. data/lib/vines/cluster/subscriber.rb +108 -0
  16. data/lib/vines/cluster.rb +246 -0
  17. data/lib/vines/command/bcrypt.rb +12 -0
  18. data/lib/vines/command/cert.rb +50 -0
  19. data/lib/vines/command/init.rb +68 -0
  20. data/lib/vines/command/ldap.rb +38 -0
  21. data/lib/vines/command/restart.rb +12 -0
  22. data/lib/vines/command/schema.rb +24 -0
  23. data/lib/vines/command/start.rb +28 -0
  24. data/lib/vines/command/stop.rb +18 -0
  25. data/lib/vines/config/host.rb +125 -0
  26. data/lib/vines/config/port.rb +132 -0
  27. data/lib/vines/config/pubsub.rb +108 -0
  28. data/lib/vines/config.rb +223 -0
  29. data/lib/vines/contact.rb +111 -0
  30. data/lib/vines/daemon.rb +78 -0
  31. data/lib/vines/error.rb +150 -0
  32. data/lib/vines/jid.rb +95 -0
  33. data/lib/vines/kit.rb +23 -0
  34. data/lib/vines/log.rb +24 -0
  35. data/lib/vines/router.rb +179 -0
  36. data/lib/vines/stanza/iq/auth.rb +18 -0
  37. data/lib/vines/stanza/iq/disco_info.rb +45 -0
  38. data/lib/vines/stanza/iq/disco_items.rb +29 -0
  39. data/lib/vines/stanza/iq/error.rb +16 -0
  40. data/lib/vines/stanza/iq/ping.rb +16 -0
  41. data/lib/vines/stanza/iq/private_storage.rb +83 -0
  42. data/lib/vines/stanza/iq/query.rb +10 -0
  43. data/lib/vines/stanza/iq/result.rb +16 -0
  44. data/lib/vines/stanza/iq/roster.rb +140 -0
  45. data/lib/vines/stanza/iq/session.rb +17 -0
  46. data/lib/vines/stanza/iq/vcard.rb +56 -0
  47. data/lib/vines/stanza/iq/version.rb +25 -0
  48. data/lib/vines/stanza/iq.rb +48 -0
  49. data/lib/vines/stanza/message.rb +40 -0
  50. data/lib/vines/stanza/presence/error.rb +23 -0
  51. data/lib/vines/stanza/presence/probe.rb +37 -0
  52. data/lib/vines/stanza/presence/subscribe.rb +42 -0
  53. data/lib/vines/stanza/presence/subscribed.rb +51 -0
  54. data/lib/vines/stanza/presence/unavailable.rb +15 -0
  55. data/lib/vines/stanza/presence/unsubscribe.rb +38 -0
  56. data/lib/vines/stanza/presence/unsubscribed.rb +38 -0
  57. data/lib/vines/stanza/presence.rb +141 -0
  58. data/lib/vines/stanza/pubsub/create.rb +39 -0
  59. data/lib/vines/stanza/pubsub/delete.rb +41 -0
  60. data/lib/vines/stanza/pubsub/publish.rb +66 -0
  61. data/lib/vines/stanza/pubsub/subscribe.rb +44 -0
  62. data/lib/vines/stanza/pubsub/unsubscribe.rb +30 -0
  63. data/lib/vines/stanza/pubsub.rb +22 -0
  64. data/lib/vines/stanza.rb +175 -0
  65. data/lib/vines/storage/ldap.rb +71 -0
  66. data/lib/vines/storage/local.rb +139 -0
  67. data/lib/vines/storage/null.rb +39 -0
  68. data/lib/vines/storage/sql.rb +138 -0
  69. data/lib/vines/storage.rb +239 -0
  70. data/lib/vines/store.rb +110 -0
  71. data/lib/vines/stream/client/auth.rb +74 -0
  72. data/lib/vines/stream/client/auth_restart.rb +29 -0
  73. data/lib/vines/stream/client/bind.rb +72 -0
  74. data/lib/vines/stream/client/bind_restart.rb +24 -0
  75. data/lib/vines/stream/client/closed.rb +13 -0
  76. data/lib/vines/stream/client/ready.rb +17 -0
  77. data/lib/vines/stream/client/session.rb +210 -0
  78. data/lib/vines/stream/client/start.rb +27 -0
  79. data/lib/vines/stream/client/tls.rb +38 -0
  80. data/lib/vines/stream/client.rb +84 -0
  81. data/lib/vines/stream/component/handshake.rb +26 -0
  82. data/lib/vines/stream/component/ready.rb +23 -0
  83. data/lib/vines/stream/component/start.rb +19 -0
  84. data/lib/vines/stream/component.rb +58 -0
  85. data/lib/vines/stream/http/auth.rb +22 -0
  86. data/lib/vines/stream/http/bind.rb +32 -0
  87. data/lib/vines/stream/http/bind_restart.rb +37 -0
  88. data/lib/vines/stream/http/ready.rb +29 -0
  89. data/lib/vines/stream/http/request.rb +172 -0
  90. data/lib/vines/stream/http/session.rb +120 -0
  91. data/lib/vines/stream/http/sessions.rb +65 -0
  92. data/lib/vines/stream/http/start.rb +23 -0
  93. data/lib/vines/stream/http.rb +157 -0
  94. data/lib/vines/stream/parser.rb +79 -0
  95. data/lib/vines/stream/sasl.rb +128 -0
  96. data/lib/vines/stream/server/auth.rb +13 -0
  97. data/lib/vines/stream/server/auth_restart.rb +13 -0
  98. data/lib/vines/stream/server/final_restart.rb +21 -0
  99. data/lib/vines/stream/server/outbound/auth.rb +31 -0
  100. data/lib/vines/stream/server/outbound/auth_restart.rb +20 -0
  101. data/lib/vines/stream/server/outbound/auth_result.rb +32 -0
  102. data/lib/vines/stream/server/outbound/final_features.rb +28 -0
  103. data/lib/vines/stream/server/outbound/final_restart.rb +20 -0
  104. data/lib/vines/stream/server/outbound/start.rb +20 -0
  105. data/lib/vines/stream/server/outbound/tls.rb +30 -0
  106. data/lib/vines/stream/server/outbound/tls_result.rb +34 -0
  107. data/lib/vines/stream/server/ready.rb +24 -0
  108. data/lib/vines/stream/server/start.rb +13 -0
  109. data/lib/vines/stream/server/tls.rb +13 -0
  110. data/lib/vines/stream/server.rb +150 -0
  111. data/lib/vines/stream/state.rb +60 -0
  112. data/lib/vines/stream.rb +247 -0
  113. data/lib/vines/token_bucket.rb +55 -0
  114. data/lib/vines/user.rb +123 -0
  115. data/lib/vines/version.rb +6 -0
  116. data/lib/vines/xmpp_server.rb +25 -0
  117. data/lib/vines.rb +203 -0
  118. data/test/cluster/publisher_test.rb +57 -0
  119. data/test/cluster/sessions_test.rb +47 -0
  120. data/test/cluster/subscriber_test.rb +109 -0
  121. data/test/config/host_test.rb +369 -0
  122. data/test/config/pubsub_test.rb +187 -0
  123. data/test/config_test.rb +732 -0
  124. data/test/contact_test.rb +102 -0
  125. data/test/error_test.rb +58 -0
  126. data/test/ext/nokogiri.rb +14 -0
  127. data/test/jid_test.rb +147 -0
  128. data/test/kit_test.rb +31 -0
  129. data/test/router_test.rb +243 -0
  130. data/test/stanza/iq/disco_info_test.rb +78 -0
  131. data/test/stanza/iq/disco_items_test.rb +49 -0
  132. data/test/stanza/iq/private_storage_test.rb +184 -0
  133. data/test/stanza/iq/roster_test.rb +229 -0
  134. data/test/stanza/iq/session_test.rb +25 -0
  135. data/test/stanza/iq/vcard_test.rb +146 -0
  136. data/test/stanza/iq/version_test.rb +64 -0
  137. data/test/stanza/iq_test.rb +70 -0
  138. data/test/stanza/message_test.rb +126 -0
  139. data/test/stanza/presence/probe_test.rb +50 -0
  140. data/test/stanza/presence/subscribe_test.rb +83 -0
  141. data/test/stanza/pubsub/create_test.rb +116 -0
  142. data/test/stanza/pubsub/delete_test.rb +169 -0
  143. data/test/stanza/pubsub/publish_test.rb +309 -0
  144. data/test/stanza/pubsub/subscribe_test.rb +205 -0
  145. data/test/stanza/pubsub/unsubscribe_test.rb +148 -0
  146. data/test/stanza_test.rb +85 -0
  147. data/test/storage/ldap_test.rb +201 -0
  148. data/test/storage/local_test.rb +59 -0
  149. data/test/storage/mock_redis.rb +97 -0
  150. data/test/storage/null_test.rb +29 -0
  151. data/test/storage/storage_tests.rb +182 -0
  152. data/test/storage_test.rb +85 -0
  153. data/test/store_test.rb +130 -0
  154. data/test/stream/client/auth_test.rb +137 -0
  155. data/test/stream/client/ready_test.rb +47 -0
  156. data/test/stream/client/session_test.rb +27 -0
  157. data/test/stream/component/handshake_test.rb +52 -0
  158. data/test/stream/component/ready_test.rb +103 -0
  159. data/test/stream/component/start_test.rb +39 -0
  160. data/test/stream/http/auth_test.rb +70 -0
  161. data/test/stream/http/ready_test.rb +86 -0
  162. data/test/stream/http/request_test.rb +209 -0
  163. data/test/stream/http/sessions_test.rb +49 -0
  164. data/test/stream/http/start_test.rb +50 -0
  165. data/test/stream/parser_test.rb +122 -0
  166. data/test/stream/sasl_test.rb +195 -0
  167. data/test/stream/server/auth_test.rb +61 -0
  168. data/test/stream/server/outbound/auth_test.rb +75 -0
  169. data/test/stream/server/ready_test.rb +98 -0
  170. data/test/test_helper.rb +42 -0
  171. data/test/token_bucket_test.rb +44 -0
  172. data/test/user_test.rb +96 -0
  173. data/vines.gemspec +30 -0
  174. metadata +387 -0
@@ -0,0 +1,85 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'test_helper'
4
+
5
+ describe Vines::Stanza do
6
+ subject { Vines::Stanza::Message.new(xml, stream) }
7
+ let(:alice) { Vines::JID.new('alice@wonderland.lit/tea') }
8
+ let(:romeo) { Vines::JID.new('romeo@verona.lit/balcony') }
9
+ let(:stream) { MiniTest::Mock.new }
10
+ let(:config) do
11
+ Vines::Config.new do
12
+ host 'wonderland.lit' do
13
+ storage(:fs) { dir Dir.tmpdir }
14
+ end
15
+ end
16
+ end
17
+
18
+ describe 'when stanza contains no addresses' do
19
+ let(:xml) { node(%Q{<message>hello!</message>}) }
20
+
21
+ it 'validates them as nil' do
22
+ subject.validate_to.must_be_nil
23
+ subject.validate_from.must_be_nil
24
+ stream.verify
25
+ end
26
+ end
27
+
28
+ describe 'when stanza contains valid addresses' do
29
+ let(:xml) { node(%Q{<message from="#{alice}" to="#{romeo}">hello!</message>}) }
30
+
31
+ it 'validates and returns JID objects' do
32
+ subject.validate_to.must_equal romeo
33
+ subject.validate_from.must_equal alice
34
+ stream.verify
35
+ end
36
+ end
37
+
38
+ describe 'when stanza contains invalid addresses' do
39
+ let(:xml) { node(%Q{<message from="a lice@wonderland.lit" to="romeo@v erona.lit">hello!</message>}) }
40
+
41
+ it 'raises a jid-malformed stanza error' do
42
+ -> { subject.validate_to }.must_raise Vines::StanzaErrors::JidMalformed
43
+ -> { subject.validate_from }.must_raise Vines::StanzaErrors::JidMalformed
44
+ stream.verify
45
+ end
46
+ end
47
+
48
+ describe 'when receiving a non-routable stanza type' do
49
+ let(:xml) { node('<auth/>') }
50
+
51
+ it 'handles locally rather than routing' do
52
+ subject.local?.must_equal true
53
+ stream.verify
54
+ end
55
+ end
56
+
57
+ describe 'when stanza is missing a to address' do
58
+ let(:xml) { node(%Q{<message>hello!</message>}) }
59
+
60
+ it 'handles locally rather than routing' do
61
+ subject.local?.must_equal true
62
+ stream.verify
63
+ end
64
+ end
65
+
66
+ describe 'when stanza is addressed to a local jid' do
67
+ let(:xml) { node(%Q{<message to="#{alice}">hello!</message>}) }
68
+
69
+ it 'handles locally rather than routing' do
70
+ stream.expect :config, config
71
+ subject.local?.must_equal true
72
+ stream.verify
73
+ end
74
+ end
75
+
76
+ describe 'when stanza is addressed to a remote jid' do
77
+ let(:xml) { node(%Q{<message to="#{romeo}">hello!</message>}) }
78
+
79
+ it 'is not considered a local stanza' do
80
+ stream.expect :config, config
81
+ subject.local?.must_equal false
82
+ stream.verify
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,201 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'test_helper'
4
+
5
+ describe Vines::Storage::Ldap do
6
+ ALICE_DN = 'uid=alice@wondlerand.lit,ou=People,dc=wonderland,dc=lit'
7
+ CONTEXT = {}
8
+
9
+ after do
10
+ CONTEXT.clear
11
+ end
12
+
13
+ describe '#initialize' do
14
+ it 'raises with missing host and port' do
15
+ assert_raises(RuntimeError) do
16
+ Vines::Storage::Ldap.new(nil, nil) do
17
+ tls true
18
+ dn 'cn=Directory Manager'
19
+ password 'secr3t'
20
+ basedn 'dc=wonderland,dc=lit'
21
+ object_class 'person'
22
+ user_attr 'uid'
23
+ name_attr 'cn'
24
+ end
25
+ end
26
+ end
27
+
28
+ it 'does not raise when using default host and port' do
29
+ Vines::Storage::Ldap.new do
30
+ tls true
31
+ dn 'cn=Directory Manager'
32
+ password 'secr3t'
33
+ basedn 'dc=wonderland,dc=lit'
34
+ object_class 'person'
35
+ user_attr 'uid'
36
+ name_attr 'cn'
37
+ end
38
+ end
39
+
40
+ it 'does not raise when given a host and port' do
41
+ Vines::Storage::Ldap.new('0.0.0.1', 42) do
42
+ tls true
43
+ dn 'cn=Directory Manager'
44
+ password 'secr3t'
45
+ basedn 'dc=wonderland,dc=lit'
46
+ object_class 'person'
47
+ user_attr 'uid'
48
+ name_attr 'cn'
49
+ end
50
+ end
51
+
52
+ it 'raises when given no parameters' do
53
+ assert_raises(RuntimeError) do
54
+ Vines::Storage::Ldap.new {}
55
+ end
56
+ end
57
+
58
+ it 'raises when given blank parameters' do
59
+ assert_raises(RuntimeError) do
60
+ Vines::Storage::Ldap.new do
61
+ tls
62
+ dn
63
+ password
64
+ basedn
65
+ object_class
66
+ user_attr
67
+ name_attr
68
+ end
69
+ end
70
+ end
71
+
72
+ it 'handles boolean false values without raising' do
73
+ Vines::Storage::Ldap.new do
74
+ tls false
75
+ dn 'cn=Directory Manager'
76
+ password 'secr3t'
77
+ basedn 'dc=wonderland,dc=lit'
78
+ object_class 'person'
79
+ user_attr 'uid'
80
+ name_attr 'cn'
81
+ end
82
+ end
83
+ end
84
+
85
+ describe '#authenticate' do
86
+ it 'fails with invalid credentials' do
87
+ ldap = connect
88
+ assert_nil ldap.authenticate(nil, nil)
89
+ assert_nil ldap.authenticate('', '')
90
+ assert_nil ldap.authenticate('alice@wonderland.lit', ' ')
91
+ assert_nil ldap.authenticate('bogus"jid@wonderland.lit', 'password')
92
+ assert_nil ldap.authenticate(Vines::JID.new('alice@wonderland.lit'), '')
93
+ end
94
+
95
+ it 'fails when user not found' do
96
+ CONTEXT.merge!({
97
+ :mock_ldap => MiniTest::Mock.new,
98
+ :connect_calls => 0
99
+ })
100
+ ldap = connect
101
+ def ldap.connect(dn, password)
102
+ CONTEXT[:connect_calls] += 1
103
+ raise unless 'cn=Directory Manager' == dn
104
+ raise unless 'secr3t' == password
105
+
106
+ clas = Net::LDAP::Filter.eq('objectClass', 'person')
107
+ uid = Net::LDAP::Filter.eq('uid', 'alice@wonderland.lit')
108
+
109
+ mock = CONTEXT[:mock_ldap]
110
+ mock.expect(:search, [], [{:attributes => ['cn', 'mail'], :filter => clas & uid}])
111
+ end
112
+ assert_nil ldap.authenticate('alice@wonderland.lit', 'passw0rd')
113
+ assert CONTEXT[:mock_ldap]
114
+ assert_equal 1, CONTEXT[:connect_calls]
115
+ end
116
+
117
+ it 'fails when user cannot bind' do
118
+ CONTEXT.merge!({
119
+ :credentials => [
120
+ {:dn => 'cn=Directory Manager', :password => 'secr3t'},
121
+ {:dn => ALICE_DN, :password => 'passw0rd'}
122
+ ],
123
+ :mock_ldap => MiniTest::Mock.new,
124
+ :mock_entry => MiniTest::Mock.new,
125
+ :connect_calls => 0
126
+ })
127
+ ldap = connect
128
+ def ldap.connect(dn, password)
129
+ credentials = CONTEXT[:credentials][CONTEXT[:connect_calls]]
130
+ CONTEXT[:connect_calls] += 1
131
+ raise unless credentials[:dn] == dn && credentials[:password] == password
132
+
133
+ clas = Net::LDAP::Filter.eq('objectClass', 'person')
134
+ uid = Net::LDAP::Filter.eq('uid', 'alice@wonderland.lit')
135
+
136
+ entry = CONTEXT[:mock_entry]
137
+ entry.expect(:dn, ALICE_DN)
138
+
139
+ mock = CONTEXT[:mock_ldap]
140
+ mock.expect(:search, [entry], [{:attributes => ['cn', 'mail'], :filter => clas & uid}])
141
+ mock.expect(:bind, false)
142
+ end
143
+ assert_nil ldap.authenticate('alice@wonderland.lit', 'passw0rd')
144
+ assert_equal 2, CONTEXT[:connect_calls]
145
+ assert CONTEXT[:mock_entry].verify
146
+ assert CONTEXT[:mock_ldap].verify
147
+ end
148
+
149
+ it 'authenticates successfully with proper credentials' do
150
+ CONTEXT.merge!({
151
+ :credentials => [
152
+ {:dn => 'cn=Directory Manager', :password => 'secr3t'},
153
+ {:dn => ALICE_DN, :password => 'passw0rd'}
154
+ ],
155
+ :mock_ldap => MiniTest::Mock.new,
156
+ :mock_entry => MiniTest::Mock.new,
157
+ :connect_calls => 0
158
+ })
159
+ ldap = connect
160
+ def ldap.connect(dn, password)
161
+ credentials = CONTEXT[:credentials][CONTEXT[:connect_calls]]
162
+ CONTEXT[:connect_calls] += 1
163
+ raise unless credentials[:dn] == dn && credentials[:password] == password
164
+
165
+ clas = Net::LDAP::Filter.eq('objectClass', 'person')
166
+ uid = Net::LDAP::Filter.eq('uid', 'alice@wonderland.lit')
167
+
168
+ entry = CONTEXT[:mock_entry]
169
+ entry.expect(:dn, ALICE_DN)
170
+ entry.expect(:[], ['Alice Liddell'], ['cn'])
171
+
172
+ mock = CONTEXT[:mock_ldap]
173
+ mock.expect(:search, [entry], [{:attributes => ['cn', 'mail'], :filter => clas & uid}])
174
+ mock.expect(:bind, true)
175
+ end
176
+ user = ldap.authenticate('alice@wonderland.lit', 'passw0rd')
177
+ refute_nil user
178
+ assert_equal 'alice@wonderland.lit', user.jid.to_s
179
+ assert_equal 'Alice Liddell', user.name
180
+ assert_equal [], user.roster
181
+
182
+ assert_equal 2, CONTEXT[:connect_calls]
183
+ assert CONTEXT[:mock_entry].verify
184
+ assert CONTEXT[:mock_ldap].verify
185
+ end
186
+ end
187
+
188
+ private
189
+
190
+ def connect
191
+ Vines::Storage::Ldap.new('0.0.0.0', 636) do
192
+ tls true
193
+ dn 'cn=Directory Manager'
194
+ password 'secr3t'
195
+ basedn 'dc=wonderland,dc=lit'
196
+ object_class 'person'
197
+ user_attr 'uid'
198
+ name_attr 'cn'
199
+ end
200
+ end
201
+ end
@@ -0,0 +1,59 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'storage_tests'
4
+ require 'test_helper'
5
+
6
+ describe Vines::Storage::Local do
7
+ include StorageTests
8
+
9
+ DIR = Dir.mktmpdir
10
+
11
+ def setup
12
+ Dir.mkdir(DIR) unless File.exists?(DIR)
13
+ %w[user vcard fragment].each do |d|
14
+ Dir.mkdir(File.join(DIR, d))
15
+ end
16
+
17
+ files = {
18
+ :empty => "#{DIR}/user/empty@wonderland.lit",
19
+ :no_pass => "#{DIR}/user/no_password@wonderland.lit",
20
+ :clear_pass => "#{DIR}/user/clear_password@wonderland.lit",
21
+ :bcrypt => "#{DIR}/user/bcrypt_password@wonderland.lit",
22
+ :full => "#{DIR}/user/full@wonderland.lit",
23
+ :vcard => "#{DIR}/vcard/full@wonderland.lit",
24
+ :fragment => "#{DIR}/fragment/full@wonderland.lit-#{StorageTests::FRAGMENT_ID}"
25
+ }
26
+ File.open(files[:empty], 'w') {|f| f.write('') }
27
+ File.open(files[:no_pass], 'w') {|f| f.write('foo: bar') }
28
+ File.open(files[:clear_pass], 'w') {|f| f.write('password: secret') }
29
+ File.open(files[:bcrypt], 'w') {|f| f.write("password: #{BCrypt::Password.create('secret')}") }
30
+ File.open(files[:full], 'w') do |f|
31
+ f.puts("password: #{BCrypt::Password.create('secret')}")
32
+ f.puts("name: Tester")
33
+ f.puts("roster:")
34
+ f.puts(" contact1@wonderland.lit:")
35
+ f.puts(" name: Contact1")
36
+ f.puts(" groups: [Group1, Group2]")
37
+ f.puts(" contact2@wonderland.lit:")
38
+ f.puts(" name: Contact2")
39
+ f.puts(" groups: [Group3, Group4]")
40
+ end
41
+ File.open(files[:vcard], 'w') {|f| f.write(StorageTests::VCARD.to_xml) }
42
+ File.open(files[:fragment], 'w') {|f| f.write(StorageTests::FRAGMENT.to_xml) }
43
+ end
44
+
45
+ def teardown
46
+ FileUtils.remove_entry_secure(DIR)
47
+ end
48
+
49
+ def storage
50
+ Vines::Storage::Local.new { dir DIR }
51
+ end
52
+
53
+ def test_init
54
+ assert_raises(RuntimeError) { Vines::Storage::Local.new {} }
55
+ assert_raises(RuntimeError) { Vines::Storage::Local.new { dir 'bogus' } }
56
+ assert_raises(RuntimeError) { Vines::Storage::Local.new { dir '/sbin' } }
57
+ Vines::Storage::Local.new { dir DIR } # shouldn't raise an error
58
+ end
59
+ end
@@ -0,0 +1,97 @@
1
+ # encoding: UTF-8
2
+
3
+ # A mock redis storage implementation that saves data to an in-memory Hash.
4
+ class MockRedis
5
+ attr_reader :db
6
+
7
+ # Mimic em-hiredis behavior.
8
+ def self.defer(method)
9
+ old = instance_method(method)
10
+ define_method method do |*args, &block|
11
+ result = old.bind(self).call(*args)
12
+ deferred = EM::DefaultDeferrable.new
13
+ deferred.callback(&block) if block
14
+ EM.next_tick { deferred.succeed(result) }
15
+ deferred
16
+ end
17
+ end
18
+
19
+ def initialize
20
+ @db = {}
21
+ end
22
+
23
+ def del(key)
24
+ @db.delete(key)
25
+ end
26
+ defer :del
27
+
28
+ def get(key)
29
+ @db[key]
30
+ end
31
+ defer :get
32
+
33
+ def set(key, value)
34
+ @db[key] = value
35
+ end
36
+ defer :set
37
+
38
+ def hget(key, field)
39
+ @db[key][field] rescue nil
40
+ end
41
+ defer :hget
42
+
43
+ def hdel(key, field)
44
+ @db[key].delete(field) rescue nil
45
+ end
46
+ defer :hdel
47
+
48
+ def hgetall(key)
49
+ (@db[key] || {}).map do |k, v|
50
+ [k, v]
51
+ end.flatten
52
+ end
53
+ defer :hgetall
54
+
55
+ def hset(key, field, value)
56
+ @db[key] ||= {}
57
+ @db[key][field] = value
58
+ end
59
+ defer :hset
60
+
61
+ def hmset(key, *args)
62
+ @db[key] = Hash[*args]
63
+ end
64
+ defer :hmset
65
+
66
+ def sadd(key, obj)
67
+ @db[key] ||= Set.new
68
+ @db[key] << obj
69
+ end
70
+ defer :sadd
71
+
72
+ def srem(key, obj)
73
+ @db[key].delete(obj) rescue nil
74
+ end
75
+ defer :srem
76
+
77
+ def smembers
78
+ @db[key].to_a rescue []
79
+ end
80
+ defer :smembers
81
+
82
+ def flushdb
83
+ @db.clear
84
+ end
85
+ defer :flushdb
86
+
87
+ def multi
88
+ @transaction = true
89
+ end
90
+ defer :multi
91
+
92
+ def exec
93
+ raise 'transaction must start with multi' unless @transaction
94
+ @transaction = false
95
+ end
96
+ defer :exec
97
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'test_helper'
4
+
5
+ describe Vines::Storage::Null do
6
+ before do
7
+ @storage = Vines::Storage::Null.new
8
+ @user = Vines::User.new(jid: 'alice@wonderland.lit')
9
+ end
10
+
11
+ def test_find_user_returns_nil
12
+ assert_nil @storage.find_user(@user.jid)
13
+ @storage.save_user(@user)
14
+ assert_nil @storage.find_user(@user.jid)
15
+ end
16
+
17
+ def test_find_vcard_returns_nil
18
+ assert_nil @storage.find_vcard(@user.jid)
19
+ @storage.save_vcard(@user.jid, 'card')
20
+ assert_nil @storage.find_vcard(@user.jid)
21
+ end
22
+
23
+ def test_find_fragment_returns_nil
24
+ assert_nil @storage.find_fragment(@user.jid, 'node')
25
+ @storage.save_fragment(@user.jid, 'node')
26
+ assert_nil @storage.find_fragment(@user.jid, 'node')
27
+ nil
28
+ end
29
+ end
@@ -0,0 +1,182 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'test_helper'
4
+
5
+ # Mixin methods for storage implementation test classes. The behavioral
6
+ # tests are the same regardless of implementation so share those methods
7
+ # here.
8
+ module StorageTests
9
+ FRAGMENT_ID = Digest::SHA1.hexdigest("characters:urn:wonderland")
10
+
11
+ FRAGMENT = Nokogiri::XML(%q{
12
+ <characters xmlns="urn:wonderland">
13
+ <character>Alice</character>
14
+ </characters>
15
+ }.strip).root
16
+
17
+ VCARD = Nokogiri::XML(%q{
18
+ <vCard xmlns="vcard-temp">
19
+ <FN>Alice in Wonderland</FN>
20
+ </vCard>
21
+ }.strip).root
22
+
23
+ class EMLoop
24
+ def initialize
25
+ EM.run do
26
+ Fiber.new do
27
+ yield
28
+ EM.stop
29
+ end.resume
30
+ end
31
+ end
32
+ end
33
+
34
+ def test_authenticate
35
+ EMLoop.new do
36
+ db = storage
37
+ assert_nil db.authenticate(nil, nil)
38
+ assert_nil db.authenticate(nil, 'secret')
39
+ assert_nil db.authenticate('bogus', nil)
40
+ assert_nil db.authenticate('bogus', 'secret')
41
+ assert_nil db.authenticate('empty@wonderland.lit', 'secret')
42
+ assert_nil db.authenticate('no_password@wonderland.lit', 'secret')
43
+ assert_nil db.authenticate('clear_password@wonderland.lit', 'secret')
44
+
45
+ user = db.authenticate('bcrypt_password@wonderland.lit', 'secret')
46
+ refute_nil user
47
+ assert_equal('bcrypt_password@wonderland.lit', user.jid.to_s)
48
+
49
+ user = db.authenticate('full@wonderland.lit', 'secret')
50
+ refute_nil user
51
+ assert_equal 'Tester', user.name
52
+ assert_equal 'full@wonderland.lit', user.jid.to_s
53
+
54
+ assert_equal 2, user.roster.length
55
+ assert_equal 'contact1@wonderland.lit', user.roster[0].jid.to_s
56
+ assert_equal 'Contact1', user.roster[0].name
57
+ assert_equal 2, user.roster[0].groups.length
58
+ assert_equal 'Group1', user.roster[0].groups[0]
59
+ assert_equal 'Group2', user.roster[0].groups[1]
60
+
61
+ assert_equal 'contact2@wonderland.lit', user.roster[1].jid.to_s
62
+ assert_equal 'Contact2', user.roster[1].name
63
+ assert_equal 2, user.roster[1].groups.length
64
+ assert_equal 'Group3', user.roster[1].groups[0]
65
+ assert_equal 'Group4', user.roster[1].groups[1]
66
+ end
67
+ end
68
+
69
+ def test_find_user
70
+ EMLoop.new do
71
+ db = storage
72
+ user = db.find_user(nil)
73
+ assert_nil user
74
+
75
+ user = db.find_user('full@wonderland.lit')
76
+ refute_nil user
77
+ assert_equal 'full@wonderland.lit', user.jid.to_s
78
+
79
+ user = db.find_user(Vines::JID.new('full@wonderland.lit'))
80
+ refute_nil user
81
+ assert_equal 'full@wonderland.lit', user.jid.to_s
82
+
83
+ user = db.find_user(Vines::JID.new('full@wonderland.lit/resource'))
84
+ refute_nil user
85
+ assert_equal 'full@wonderland.lit', user.jid.to_s
86
+ end
87
+ end
88
+
89
+ def test_save_user
90
+ EMLoop.new do
91
+ db = storage
92
+ user = Vines::User.new(
93
+ :jid => 'save_user@domain.tld/resource1',
94
+ :name => 'Save User',
95
+ :password => 'secret')
96
+ user.roster << Vines::Contact.new(
97
+ :jid => 'contact1@domain.tld/resource2',
98
+ :name => 'Contact 1')
99
+ db.save_user(user)
100
+ user = db.find_user('save_user@domain.tld')
101
+ refute_nil user
102
+ assert_equal 'save_user@domain.tld', user.jid.to_s
103
+ assert_equal 'Save User', user.name
104
+ assert_equal 1, user.roster.length
105
+ assert_equal 'contact1@domain.tld', user.roster[0].jid.to_s
106
+ assert_equal 'Contact 1', user.roster[0].name
107
+ end
108
+ end
109
+
110
+ def test_find_vcard
111
+ EMLoop.new do
112
+ db = storage
113
+ card = db.find_vcard(nil)
114
+ assert_nil card
115
+
116
+ card = db.find_vcard('full@wonderland.lit')
117
+ refute_nil card
118
+ assert_equal VCARD, card
119
+
120
+ card = db.find_vcard(Vines::JID.new('full@wonderland.lit'))
121
+ refute_nil card
122
+ assert_equal VCARD, card
123
+
124
+ card = db.find_vcard(Vines::JID.new('full@wonderland.lit/resource'))
125
+ refute_nil card
126
+ assert_equal VCARD, card
127
+ end
128
+ end
129
+
130
+ def test_save_vcard
131
+ EMLoop.new do
132
+ db = storage
133
+ db.save_user(Vines::User.new(:jid => 'save_user@domain.tld'))
134
+ db.save_vcard('save_user@domain.tld/resource1', VCARD)
135
+ card = db.find_vcard('save_user@domain.tld')
136
+ refute_nil card
137
+ assert_equal VCARD, card
138
+ end
139
+ end
140
+
141
+ def test_find_fragment
142
+ EMLoop.new do
143
+ db = storage
144
+ root = Nokogiri::XML(%q{<characters xmlns="urn:wonderland"/>}).root
145
+ bad_name = Nokogiri::XML(%q{<not_characters xmlns="urn:wonderland"/>}).root
146
+ bad_ns = Nokogiri::XML(%q{<characters xmlns="not:wonderland"/>}).root
147
+
148
+ node = db.find_fragment(nil, nil)
149
+ assert_nil node
150
+
151
+ node = db.find_fragment('full@wonderland.lit', bad_name)
152
+ assert_nil node
153
+
154
+ node = db.find_fragment('full@wonderland.lit', bad_ns)
155
+ assert_nil node
156
+
157
+ node = db.find_fragment('full@wonderland.lit', root)
158
+ refute_nil node
159
+ assert_equal FRAGMENT, node
160
+
161
+ node = db.find_fragment(Vines::JID.new('full@wonderland.lit'), root)
162
+ refute_nil node
163
+ assert_equal FRAGMENT, node
164
+
165
+ node = db.find_fragment(Vines::JID.new('full@wonderland.lit/resource'), root)
166
+ refute_nil node
167
+ assert_equal FRAGMENT, node
168
+ end
169
+ end
170
+
171
+ def test_save_fragment
172
+ EMLoop.new do
173
+ db = storage
174
+ root = Nokogiri::XML(%q{<characters xmlns="urn:wonderland"/>}).root
175
+ db.save_user(Vines::User.new(:jid => 'save_user@domain.tld'))
176
+ db.save_fragment('save_user@domain.tld/resource1', FRAGMENT)
177
+ node = db.find_fragment('save_user@domain.tld', root)
178
+ refute_nil node
179
+ assert_equal FRAGMENT, node
180
+ end
181
+ end
182
+ end