lygneo-vines 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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/daemon.rb +78 -0
  30. data/lib/vines/error.rb +150 -0
  31. data/lib/vines/follower.rb +111 -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/error_test.rb +58 -0
  125. data/test/ext/nokogiri.rb +14 -0
  126. data/test/follower_test.rb +102 -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(" follower1@wonderland.lit:")
35
+ f.puts(" name: Follower1")
36
+ f.puts(" groups: [Group1, Group2]")
37
+ f.puts(" follower2@wonderland.lit:")
38
+ f.puts(" name: Follower2")
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 'follower1@wonderland.lit', user.roster[0].jid.to_s
56
+ assert_equal 'Follower1', 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 'follower2@wonderland.lit', user.roster[1].jid.to_s
62
+ assert_equal 'Follower2', 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::Follower.new(
97
+ :jid => 'follower1@domain.tld/resource2',
98
+ :name => 'Follower 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 'follower1@domain.tld', user.roster[0].jid.to_s
106
+ assert_equal 'Follower 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