1 $:.unshift File.join(File.dirname(__FILE__), '../lib')
6 require 'rbot/journal/postgres.rb'
7 require 'rbot/journal/mongo.rb'
13 class JournalMessageTest < Test::Unit::TestCase
15 include Irc::Bot::Journal
18 m = JournalMessage.create('foo', {'bar': 42, 'baz': nil, 'qux': {'quxx': 23}})
19 assert_equal(42, m.get('bar'))
20 assert_raise ArgumentError do
23 assert_nil(m.get('nope', nil))
24 assert_nil(m.get('baz'))
25 assert_equal(23, m['qux.quxx'])
26 assert_equal(nil, m['qux.nope'])
27 assert_raise(ArgumentError) { m.get('qux.nope') }
32 class QueryTest < Test::Unit::TestCase
34 include Irc::Bot::Journal
42 topic 'log.core', 'baz'
43 timestamp from: Time.now, to: Time.now + 60 * 10
44 payload 'action': :privmsg, 'alice': 'bob'
45 payload 'channel': '#rbot'
46 payload 'foo.bar': 'baz'
48 assert_equal(['foo', 'bar', 'baz'], q.id)
49 assert_equal(['log.irc.*', 'log.core', 'baz'], q.topic)
50 assert_equal([:from, :to], q.timestamp.keys)
51 assert_equal(Time, q.timestamp[:to].class)
52 assert_equal(Time, q.timestamp[:from].class)
54 'action': :privmsg, 'alice': 'bob',
61 def test_topic_matches
65 assert_true(q.topic_matches?('foo'))
66 assert_false(q.topic_matches?('bar'))
67 assert_false(q.topic_matches?('foo.bar'))
72 assert_false(q.topic_matches?('foo'))
73 assert_false(q.topic_matches?('bar'))
74 assert_true(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'))
82 assert_true(q.topic_matches?('foo.baz'))
87 assert_false(q.topic_matches?('foo'))
88 assert_false(q.topic_matches?('bar'))
89 assert_true(q.topic_matches?('foo.bar'))
90 assert_true(q.topic_matches?('bar.bar'))
91 assert_false(q.topic_matches?('foo.foo'))
96 assert_false(q.topic_matches?('foo'))
97 assert_true(q.topic_matches?('foo.bar'))
102 topic 'baz.alice.bob.*.foo'
104 assert_true(q.topic_matches?('foo'))
105 assert_true(q.topic_matches?('bar'))
106 assert_true(q.topic_matches?('baz.alice.bob.asdf.foo'))
107 assert_false(q.topic_matches?('baz.alice.bob..foo'))
113 topic 'log.irc.*', 'log.core'
114 timestamp from: Time.now - DAY, to: Time.now + DAY
115 payload 'action': 'privmsg', 'foo.bar': 'baz'
117 assert_true(q.matches? JournalMessage.create('log.irc.raw', {'action' => 'privmsg'}))
118 assert_false(q.matches? JournalMessage.create('baz', {}))
119 assert_true(q.matches? JournalMessage.create('log.core', {foo: {bar: 'baz'}}))
121 # tests timestamp from/to:
122 assert_true(q.matches? JournalMessage.new(
126 payload: {action: 'privmsg'}))
127 assert_false(q.matches? JournalMessage.new(
130 timestamp: Time.now - DAY*3,
131 payload: {action: 'privmsg'}))
132 assert_false(q.matches? JournalMessage.new(
135 timestamp: Time.now + DAY*3,
136 payload: {action: 'privmsg'}))
141 class JournalBrokerTest < Test::Unit::TestCase
143 include Irc::Bot::Journal
147 journal = JournalBroker.new(consumer: Proc.new { |message|
151 # publish some messages:
152 journal.publish 'log.irc',
153 source: 'alice', message: '<3 pg'
154 journal.publish 'log.irc',
155 source: 'bob', message: 'mysql > pg'
156 journal.publish 'log.irc',
157 source: 'alice', target: 'bob', action: :kick
159 # wait for messages to be consumed:
161 assert_equal(3, received.length)
166 journal = JournalBroker.new
168 # subscribe to messages for topic foo:
169 sub = journal.subscribe('foo') do |message|
173 # publish some messages:
174 journal.publish 'foo', {}
175 journal.publish 'bar', {}
176 journal.publish 'foo', {}
178 # wait for messages to be consumed:
180 assert_equal(2, received.length)
184 journal.publish 'foo', {}
187 journal.publish 'foo', {}
189 assert_equal(1, received.length)
194 module JournalStorageTestMixin
196 include Irc::Bot::Journal
204 m = JournalMessage.create('log.core', {foo: {bar: 'baz', qux: 42}})
208 res = @storage.find(Query.define { id m.id })
209 assert_equal(1, res.length)
210 assert_equal(m, res.first)
212 # check timestamp was returned correctly:
213 assert_equal(m.timestamp.strftime('%Y-%m-%d %H:%M:%S%z'),
214 res.first.timestamp.strftime('%Y-%m-%d %H:%M:%S%z'))
216 # check if payload was returned correctly:
217 assert_equal({'foo' => {'bar' => 'baz', 'qux' => 42}}, res.first.payload)
220 assert_equal(m, @storage.find(Query.define { topic('log.core') }).first)
221 assert_equal(m, @storage.find(Query.define { topic('log.*') }).first)
222 assert_equal(m, @storage.find(Query.define { topic('*.*') }).first)
224 # query by timestamp range
225 assert_equal(1, @storage.find(Query.define {
226 timestamp(from: Time.now-DAY, to: Time.now+DAY) }).length)
227 assert_equal(0, @storage.find(Query.define {
228 timestamp(from: Time.now-DAY*2, to: Time.now-DAY) }).length)
231 res = @storage.find(Query.define { payload('foo.bar' => 'baz') })
232 assert_equal(m, res.first)
233 res = @storage.find(Query.define { payload('foo.bar' => 'x') })
234 assert_true(res.empty?)
236 # without arguments: find and count
237 assert_equal(1, @storage.count)
238 assert_equal(m, @storage.find.first)
242 # tests limit/offset and block parameters of find()
243 @storage.insert(JournalMessage.create('irclogs', {message: 'foo'}))
244 @storage.insert(JournalMessage.create('irclogs', {message: 'bar'}))
245 @storage.insert(JournalMessage.create('irclogs', {message: 'baz'}))
246 @storage.insert(JournalMessage.create('irclogs', {message: 'qux'}))
249 @storage.find(Query.define({topic: 'irclogs'}), 2, 1) do |m|
252 assert_equal(2, msgs.length)
253 assert_equal('bar', msgs.first['message'])
254 assert_equal('baz', msgs.last['message'])
257 @storage.find(Query.define({topic: 'irclogs'})) do |m|
260 assert_equal(4, msgs.length)
261 assert_equal('foo', msgs.first['message'])
262 assert_equal('qux', msgs.last['message'])
266 def test_operations_multiple
267 # test operations on multiple messages
269 @storage.insert(JournalMessage.create('test.topic', {name: 'one'}))
270 @storage.insert(JournalMessage.create('test.topic', {name: 'two'}))
271 @storage.insert(JournalMessage.create('test.topic', {name: 'three'}))
272 @storage.insert(JournalMessage.create('archived.topic', {name: 'four'},
273 timestamp: Time.now - DAY*100))
274 @storage.insert(JournalMessage.create('complex', {name: 'five', country: {
277 @storage.insert(JournalMessage.create('complex', {name: 'six', country: {
282 assert_equal(3, @storage.find(Query.define { topic 'test.*' }).length)
284 assert_equal(1, @storage.find(Query.define {
285 payload('country.name' => 'Austria') }).length)
286 # query by timestamp range
287 assert_equal(1, @storage.find(Query.define {
288 timestamp(from: Time.now - DAY*150, to: Time.now - DAY*50) }).length)
291 assert_equal(2, @storage.count(Query.define { topic('complex') }))
292 assert_equal(6, @storage.count)
293 @storage.remove(Query.define { topic('archived.*') })
294 assert_equal(5, @storage.count)
296 assert_equal(0, @storage.count)
299 def test_broker_interface
300 journal = JournalBroker.new(storage: @storage)
302 journal.publish 'irclogs', message: 'foo'
303 journal.publish 'irclogs', message: 'bar'
304 journal.publish 'irclogs', message: 'baz'
305 journal.publish 'irclogs', message: 'qux'
307 # wait for messages to be consumed:
311 journal.find({topic: 'irclogs'}, 2, 1) do |m|
314 assert_equal(2, msgs.length)
315 assert_equal('bar', msgs.first['message'])
316 assert_equal('baz', msgs.last['message'])
318 journal.ensure_payload_index('foo.bar.baz')
325 assert_equal(0, @storage.count)
326 # prepare messages to insert, we benchmark the storage backend not ruby
328 messages = (0...NUM).map do
330 JournalMessage.create(
331 'test.topic.num_'+num.to_s, {answer: {number: '42', word: 'forty-two'}})
334 # iter is the number of operations performed WITHIN block
335 def benchmark(label, iter, &block)
336 time = Benchmark.realtime do
339 puts label + ' %d iterations, duration: %.3fms (%.3fms / iteration)' % [iter, time*1000, (time*1000) / iter]
342 benchmark(@storage.class.to_s+'~insert', messages.length) do
348 benchmark(@storage.class.to_s+'~find_by_id', messages.length) do
350 @storage.find(Query.define { id m.id })
353 benchmark(@storage.class.to_s+'~find_by_topic', messages.length) do
355 @storage.find(Query.define { topic m.topic })
358 benchmark(@storage.class.to_s+'~find_by_topic_wildcard', messages.length) do
360 @storage.find(Query.define { topic m.topic.gsub('topic', '*') })
368 class JournalStoragePostgresTest < Test::Unit::TestCase
370 include JournalStorageTestMixin
373 @storage = Storage::PostgresStorage.new(
374 uri: ENV['PG_URI'] || 'postgresql://localhost/rbot_journal',
378 def test_query_to_sql
383 topic 'log.core', 'baz'
384 timestamp from: Time.now, to: Time.now + 60 * 10
385 payload 'action': :privmsg, 'alice': 'bob'
386 payload 'channel': '#rbot'
387 payload 'foo.bar': 'baz'
389 sql = @storage.query_to_sql(q)
390 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])
394 assert_equal('(id = $1)', @storage.query_to_sql(q)[0])
398 assert_equal('(topic ILIKE $1)', @storage.query_to_sql(q)[0])
399 assert_equal(['foo.%.bar'], @storage.query_to_sql(q)[1])
404 puts 'NOTE: Set PG_URI environment variable to test postgresql storage.'
408 class JournalStorageMongoTest < Test::Unit::TestCase
410 include JournalStorageTestMixin
413 @storage = Storage::MongoStorage.new(
414 uri: ENV['MONGO_URI'] || 'mongodb://127.0.0.1:27017/rbot',
419 puts 'NOTE: Set MONGO_URI environment variable to test postgresql storage.'