1 $:.unshift File.join(File.dirname(__FILE__), '../lib')
6 @@datadir = File.expand_path(File.dirname($0) + '/../data/rbot')
7 @@coredir = File.expand_path(File.dirname($0) + '/../lib/rbot/core')
14 require 'rbot/journal'
20 class JournalMessageTest < Test::Unit::TestCase
22 include Irc::Bot::Journal
25 m = JournalMessage.create('foo', {'bar': 42, 'baz': nil, 'qux': {'quxx': 23}})
26 assert_equal(42, m.get('bar'))
27 assert_raise ArgumentError do
30 assert_nil(m.get('nope', nil))
31 assert_nil(m.get('baz'))
32 assert_equal(23, m['qux.quxx'])
33 assert_equal(nil, m['qux.nope'])
34 assert_raise(ArgumentError) { m.get('qux.nope') }
39 class QueryTest < Test::Unit::TestCase
41 include Irc::Bot::Journal
49 topic 'log.core', 'baz'
50 timestamp from: Time.now, to: Time.now + 60 * 10
51 payload 'action': :privmsg, 'alice': 'bob'
52 payload 'channel': '#rbot'
53 payload 'foo.bar': 'baz'
55 assert_equal(['foo', 'bar', 'baz'], q.id)
56 assert_equal(['log.irc.*', 'log.core', 'baz'], q.topic)
57 assert_equal([:from, :to], q.timestamp.keys)
58 assert_equal(Time, q.timestamp[:to].class)
59 assert_equal(Time, q.timestamp[:from].class)
61 'action': :privmsg, 'alice': 'bob',
68 def test_topic_matches
72 assert_true(q.topic_matches?('foo'))
73 assert_false(q.topic_matches?('bar'))
74 assert_false(q.topic_matches?('foo.bar'))
79 assert_false(q.topic_matches?('foo'))
80 assert_false(q.topic_matches?('bar'))
81 assert_true(q.topic_matches?('foo.bar'))
86 assert_false(q.topic_matches?('foo'))
87 assert_false(q.topic_matches?('bar'))
88 assert_true(q.topic_matches?('foo.bar'))
89 assert_true(q.topic_matches?('foo.baz'))
94 assert_false(q.topic_matches?('foo'))
95 assert_false(q.topic_matches?('bar'))
96 assert_true(q.topic_matches?('foo.bar'))
97 assert_true(q.topic_matches?('bar.bar'))
98 assert_false(q.topic_matches?('foo.foo'))
103 assert_false(q.topic_matches?('foo'))
104 assert_true(q.topic_matches?('foo.bar'))
109 topic 'baz.alice.bob.*.foo'
111 assert_true(q.topic_matches?('foo'))
112 assert_true(q.topic_matches?('bar'))
113 assert_true(q.topic_matches?('baz.alice.bob.asdf.foo'))
114 assert_false(q.topic_matches?('baz.alice.bob..foo'))
120 topic 'log.irc.*', 'log.core'
121 timestamp from: Time.now - DAY, to: Time.now + DAY
122 payload 'action': 'privmsg', 'foo.bar': 'baz'
124 assert_true(q.matches? JournalMessage.create('log.irc.raw', {'action' => 'privmsg'}))
125 assert_false(q.matches? JournalMessage.create('baz', {}))
126 assert_true(q.matches? JournalMessage.create('log.core', {foo: {bar: 'baz'}}))
128 # tests timestamp from/to:
129 assert_true(q.matches? JournalMessage.new(
133 payload: {action: 'privmsg'}))
134 assert_false(q.matches? JournalMessage.new(
137 timestamp: Time.now - DAY*3,
138 payload: {action: 'privmsg'}))
139 assert_false(q.matches? JournalMessage.new(
142 timestamp: Time.now + DAY*3,
143 payload: {action: 'privmsg'}))
148 class JournalBrokerTest < Test::Unit::TestCase
150 include Irc::Bot::Journal
154 journal = JournalBroker.new(consumer: Proc.new { |message|
158 # publish some messages:
159 journal.publish 'log.irc',
160 source: 'alice', message: '<3 pg'
161 journal.publish 'log.irc',
162 source: 'bob', message: 'mysql > pg'
163 journal.publish 'log.irc',
164 source: 'alice', target: 'bob', action: :kick
166 # wait for messages to be consumed:
168 assert_equal(3, received.length)
173 journal = JournalBroker.new
175 # subscribe to messages for topic foo:
176 sub = journal.subscribe('foo') do |message|
180 # publish some messages:
181 journal.publish 'foo', {}
182 journal.publish 'bar', {}
183 journal.publish 'foo', {}
185 # wait for messages to be consumed:
187 assert_equal(2, received.length)
191 journal.publish 'foo', {}
194 journal.publish 'foo', {}
196 assert_equal(1, received.length)
201 module JournalStorageTestMixin
203 include Irc::Bot::Journal
211 m = JournalMessage.create('log.core', {foo: {bar: 'baz', qux: 42}})
215 res = @storage.find(Query.define { id m.id })
216 assert_equal(1, res.length)
217 assert_equal(m, res.first)
219 # check timestamp was returned correctly:
220 assert_equal(m.timestamp.strftime('%Y-%m-%d %H:%M:%S%z'),
221 res.first.timestamp.strftime('%Y-%m-%d %H:%M:%S%z'))
223 # check if payload was returned correctly:
224 assert_equal({'foo' => {'bar' => 'baz', 'qux' => 42}}, res.first.payload)
227 assert_equal(m, @storage.find(Query.define { topic('log.core') }).first)
228 assert_equal(m, @storage.find(Query.define { topic('log.*') }).first)
229 assert_equal(m, @storage.find(Query.define { topic('*.*') }).first)
231 # query by timestamp range
232 assert_equal(1, @storage.find(Query.define {
233 timestamp(from: Time.now-DAY, to: Time.now+DAY) }).length)
234 assert_equal(0, @storage.find(Query.define {
235 timestamp(from: Time.now-DAY*2, to: Time.now-DAY) }).length)
238 res = @storage.find(Query.define { payload('foo.bar' => 'baz') })
239 assert_equal(m, res.first)
240 res = @storage.find(Query.define { payload('foo.bar' => 'x') })
241 assert_true(res.empty?)
243 # without arguments: find and count
244 assert_equal(1, @storage.count)
245 assert_equal(m, @storage.find.first)
249 # tests limit/offset and block parameters of find()
250 @storage.insert(JournalMessage.create('irclogs', {message: 'foo'}))
251 @storage.insert(JournalMessage.create('irclogs', {message: 'bar'}))
252 @storage.insert(JournalMessage.create('irclogs', {message: 'baz'}))
253 @storage.insert(JournalMessage.create('irclogs', {message: 'qux'}))
256 @storage.find(Query.define({topic: 'irclogs'}), 2, 1) do |m|
259 assert_equal(2, msgs.length)
260 assert_equal('bar', msgs.first['message'])
261 assert_equal('baz', msgs.last['message'])
264 @storage.find(Query.define({topic: 'irclogs'})) do |m|
267 assert_equal(4, msgs.length)
268 assert_equal('foo', msgs.first['message'])
269 assert_equal('qux', msgs.last['message'])
273 def test_operations_multiple
274 # test operations on multiple messages
276 @storage.insert(JournalMessage.create('test.topic', {name: 'one'}))
277 @storage.insert(JournalMessage.create('test.topic', {name: 'two'}))
278 @storage.insert(JournalMessage.create('test.topic', {name: 'three'}))
279 @storage.insert(JournalMessage.create('archived.topic', {name: 'four'},
280 timestamp: Time.now - DAY*100))
281 @storage.insert(JournalMessage.create('complex', {name: 'five', country: {
284 @storage.insert(JournalMessage.create('complex', {name: 'six', country: {
289 assert_equal(3, @storage.find(Query.define { topic 'test.*' }).length)
291 assert_equal(1, @storage.find(Query.define {
292 payload('country.name' => 'Austria') }).length)
293 # query by timestamp range
294 assert_equal(1, @storage.find(Query.define {
295 timestamp(from: Time.now - DAY*150, to: Time.now - DAY*50) }).length)
298 assert_equal(2, @storage.count(Query.define { topic('complex') }))
299 assert_equal(6, @storage.count)
300 @storage.remove(Query.define { topic('archived.*') })
301 assert_equal(5, @storage.count)
303 assert_equal(0, @storage.count)
306 def test_broker_interface
307 journal = JournalBroker.new(storage: @storage)
309 journal.publish 'irclogs', message: 'foo'
310 journal.publish 'irclogs', message: 'bar'
311 journal.publish 'irclogs', message: 'baz'
312 journal.publish 'irclogs', message: 'qux'
314 # wait for messages to be consumed:
318 journal.find({topic: 'irclogs'}, 2, 1) do |m|
321 assert_equal(2, msgs.length)
322 assert_equal('bar', msgs.first['message'])
323 assert_equal('baz', msgs.last['message'])
325 journal.ensure_payload_index('foo.bar.baz')
332 assert_equal(0, @storage.count)
333 # prepare messages to insert, we benchmark the storage backend not ruby
335 messages = (0...NUM).map do
337 JournalMessage.create(
338 'test.topic.num_'+num.to_s, {answer: {number: '42', word: 'forty-two'}})
341 # iter is the number of operations performed WITHIN block
342 def benchmark(label, iter, &block)
343 time = Benchmark.realtime do
346 puts label + ' %d iterations, duration: %.3fms (%.3fms / iteration)' % [iter, time*1000, (time*1000) / iter]
349 benchmark(@storage.class.to_s+'~insert', messages.length) do
355 benchmark(@storage.class.to_s+'~find_by_id', messages.length) do
357 @storage.find(Query.define { id m.id })
360 benchmark(@storage.class.to_s+'~find_by_topic', messages.length) do
362 @storage.find(Query.define { topic m.topic })
365 benchmark(@storage.class.to_s+'~find_by_topic_wildcard', messages.length) do
367 @storage.find(Query.define { topic m.topic.gsub('topic', '*') })
375 require 'rbot/journal/postgres.rb'
377 class JournalStoragePostgresTest < Test::Unit::TestCase
379 include JournalStorageTestMixin
382 @storage = Storage::PostgresStorage.new(
383 uri: ENV['PG_URI'] || 'postgresql://localhost/rbot_journal',
387 def test_query_to_sql
392 topic 'log.core', 'baz'
393 timestamp from: Time.now, to: Time.now + 60 * 10
394 payload 'action': :privmsg, 'alice': 'bob'
395 payload 'channel': '#rbot'
396 payload 'foo.bar': 'baz'
398 sql = @storage.query_to_sql(q)
399 assert_equal("(id = $1 OR id = $2 OR id = $3) AND (topic ILIKE $4 OR topic ILIKE $5 OR topic ILIKE $6) AND (timestamp >= $7 AND timestamp <= $8) AND (payload->>'action' = $9 OR payload->>'alice' = $10 OR payload->>'channel' = $11 OR payload->'foo'->>'bar' = $12)", sql[0])
403 assert_equal('(id = $1)', @storage.query_to_sql(q)[0])
407 assert_equal('(topic ILIKE $1)', @storage.query_to_sql(q)[0])
408 assert_equal(['foo.%.bar'], @storage.query_to_sql(q)[1])
413 puts 'NOTE: Set PG_URI environment variable to test postgresql storage.'
415 rescue Exception; end
418 require 'rbot/journal/mongo.rb'
420 class JournalStorageMongoTest < Test::Unit::TestCase
422 include JournalStorageTestMixin
425 @storage = Storage::MongoStorage.new(
426 uri: ENV['MONGO_URI'] || 'mongodb://127.0.0.1:27017/rbot',
431 puts 'NOTE: Set MONGO_URI environment variable to test postgresql storage.'
433 rescue Exception; end