ruby_event_store 0.18.2 → 0.19.0
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.
- checksums.yaml +4 -4
- data/Makefile +1 -2
- data/lib/ruby_event_store/client.rb +23 -33
- data/lib/ruby_event_store/errors.rb +3 -2
- data/lib/ruby_event_store/event.rb +12 -1
- data/lib/ruby_event_store/in_memory_repository.rb +45 -7
- data/lib/ruby_event_store/pub_sub/broker.rb +1 -5
- data/lib/ruby_event_store/pub_sub/dispatcher.rb +14 -3
- data/lib/ruby_event_store/spec/dispatcher_lint.rb +17 -13
- data/lib/ruby_event_store/spec/event_broker_lint.rb +9 -21
- data/lib/ruby_event_store/spec/event_repository_lint.rb +374 -52
- data/lib/ruby_event_store/version.rb +1 -1
- metadata +2 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: cf38d679db05a58d03f5cf03128e1515a44ae1e1
         | 
| 4 | 
            +
              data.tar.gz: ffabae418e7ec0fb99fe6b546790a155d2a1c361
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 7ab222cbac66327aa236df8dc5c8f5b789854559d59a2d731914e031f1874f861ce9743b51a4d6c2a5f0351458f83b6fa0c6bbce16f887de69d600d8ee70a406
         | 
| 7 | 
            +
              data.tar.gz: 0b89629ee2da94623d4bb04998e0e686e424d94a1a05f306e168b1d0afb51d19ffd16ccb72d989249821bf0688a991237e99ce41d3d2746cba2cc664ea8a9455
         | 
    
        data/Makefile
    CHANGED
    
    | @@ -10,8 +10,7 @@ test: ## Run tests | |
| 10 10 |  | 
| 11 11 | 
             
            mutate: test  ## Run mutation tests
         | 
| 12 12 | 
             
            	@echo "Running mutation tests - only 100% free mutation will be accepted"
         | 
| 13 | 
            -
            	@bundle exec mutant --include lib --require ruby_event_store --use rspec "RubyEventStore*"  | 
| 14 | 
            -
            		--ignore-subject "RubyEventStore.const_missing"
         | 
| 13 | 
            +
            	@bundle exec mutant --include lib --require ruby_event_store --use rspec "RubyEventStore*" --ignore-subject "RubyEventStore.const_missing" --ignore-subject "RubyEventStore::InMemoryRepository#append_with_synchronize" --ignore-subject "RubyEventStore::InMemoryRepository#normalize_to_array" --ignore-subject "RubyEventStore::Client#normalize_to_array"
         | 
| 15 14 |  | 
| 16 15 | 
             
            build:
         | 
| 17 16 | 
             
            	@gem build -V ruby_event_store.gemspec
         | 
| @@ -12,16 +12,22 @@ module RubyEventStore | |
| 12 12 | 
             
                  @clock          = clock
         | 
| 13 13 | 
             
                end
         | 
| 14 14 |  | 
| 15 | 
            -
                def  | 
| 16 | 
            -
                  append_to_stream( | 
| 17 | 
            -
                   | 
| 15 | 
            +
                def publish_events(events, stream_name: GLOBAL_STREAM, expected_version: :any)
         | 
| 16 | 
            +
                  append_to_stream(events, stream_name: stream_name, expected_version: expected_version)
         | 
| 17 | 
            +
                  events.each do |ev|
         | 
| 18 | 
            +
                    event_broker.notify_subscribers(ev)
         | 
| 19 | 
            +
                  end
         | 
| 18 20 | 
             
                  :ok
         | 
| 19 21 | 
             
                end
         | 
| 20 22 |  | 
| 21 | 
            -
                def  | 
| 22 | 
            -
                   | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 23 | 
            +
                def publish_event(event, stream_name: GLOBAL_STREAM, expected_version: :any)
         | 
| 24 | 
            +
                  publish_events([event], stream_name: stream_name, expected_version: expected_version)
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def append_to_stream(events, stream_name: GLOBAL_STREAM, expected_version: :any)
         | 
| 28 | 
            +
                  events = normalize_to_array(events)
         | 
| 29 | 
            +
                  events.each{|event| enrich_event_metadata(event) }
         | 
| 30 | 
            +
                  repository.append_to_stream(events, stream_name, expected_version)
         | 
| 25 31 | 
             
                  :ok
         | 
| 26 32 | 
             
                end
         | 
| 27 33 |  | 
| @@ -64,13 +70,13 @@ module RubyEventStore | |
| 64 70 | 
             
                end
         | 
| 65 71 |  | 
| 66 72 | 
             
                def subscribe(subscriber, event_types, &proc)
         | 
| 67 | 
            -
                  event_broker.add_subscriber( | 
| 73 | 
            +
                  event_broker.add_subscriber(subscriber, event_types).tap do |unsub|
         | 
| 68 74 | 
             
                    handle_subscribe(unsub, &proc)
         | 
| 69 75 | 
             
                  end
         | 
| 70 76 | 
             
                end
         | 
| 71 77 |  | 
| 72 78 | 
             
                def subscribe_to_all_events(subscriber, &proc)
         | 
| 73 | 
            -
                  event_broker.add_global_subscriber( | 
| 79 | 
            +
                  event_broker.add_global_subscriber(subscriber).tap do |unsub|
         | 
| 74 80 | 
             
                    handle_subscribe(unsub, &proc)
         | 
| 75 81 | 
             
                  end
         | 
| 76 82 | 
             
                end
         | 
| @@ -78,8 +84,8 @@ module RubyEventStore | |
| 78 84 | 
             
                private
         | 
| 79 85 | 
             
                attr_reader :repository, :page_size, :event_broker, :metadata_proc, :clock
         | 
| 80 86 |  | 
| 81 | 
            -
                def  | 
| 82 | 
            -
                   | 
| 87 | 
            +
                def normalize_to_array(events)
         | 
| 88 | 
            +
                  return *events
         | 
| 83 89 | 
             
                end
         | 
| 84 90 |  | 
| 85 91 | 
             
                def enrich_event_metadata(event)
         | 
| @@ -87,14 +93,15 @@ module RubyEventStore | |
| 87 93 | 
             
                  metadata[:timestamp] ||= clock.()
         | 
| 88 94 | 
             
                  metadata.merge!(metadata_proc.call || {}) if metadata_proc
         | 
| 89 95 |  | 
| 90 | 
            -
                  event.class.new(event_id: event.event_id, metadata: metadata, data: event.data)
         | 
| 96 | 
            +
                  # event.class.new(event_id: event.event_id, metadata: metadata, data: event.data)
         | 
| 91 97 | 
             
                end
         | 
| 92 98 |  | 
| 93 | 
            -
                def handle_subscribe(unsub)
         | 
| 94 | 
            -
                   | 
| 95 | 
            -
                     | 
| 99 | 
            +
                def handle_subscribe(unsub, &proc)
         | 
| 100 | 
            +
                  begin
         | 
| 101 | 
            +
                    proc.call
         | 
| 102 | 
            +
                  ensure
         | 
| 96 103 | 
             
                    unsub.()
         | 
| 97 | 
            -
                  end
         | 
| 104 | 
            +
                  end if proc
         | 
| 98 105 | 
             
                end
         | 
| 99 106 |  | 
| 100 107 | 
             
                class Page
         | 
| @@ -113,22 +120,5 @@ module RubyEventStore | |
| 113 120 | 
             
                  attr_reader :start, :count
         | 
| 114 121 | 
             
                end
         | 
| 115 122 |  | 
| 116 | 
            -
                def validate_expected_version(stream_name, expected_version)
         | 
| 117 | 
            -
                  raise InvalidExpectedVersion if expected_version.nil?
         | 
| 118 | 
            -
                  case expected_version
         | 
| 119 | 
            -
                  when :any
         | 
| 120 | 
            -
                    return
         | 
| 121 | 
            -
                  when :none
         | 
| 122 | 
            -
                    return if last_stream_event_id(stream_name).nil?
         | 
| 123 | 
            -
                  else
         | 
| 124 | 
            -
                    return if last_stream_event_id(stream_name).eql?(expected_version)
         | 
| 125 | 
            -
                  end
         | 
| 126 | 
            -
                  raise WrongExpectedEventVersion
         | 
| 127 | 
            -
                end
         | 
| 128 | 
            -
             | 
| 129 | 
            -
                def last_stream_event_id(stream_name)
         | 
| 130 | 
            -
                  last = repository.last_stream_event(stream_name)
         | 
| 131 | 
            -
                  last.event_id if last
         | 
| 132 | 
            -
                end
         | 
| 133 123 | 
             
              end
         | 
| 134 124 | 
             
            end
         | 
| @@ -6,10 +6,11 @@ module RubyEventStore | |
| 6 6 | 
             
              SubscriberNotExist         = Class.new(StandardError)
         | 
| 7 7 | 
             
              InvalidPageStart           = Class.new(ArgumentError)
         | 
| 8 8 | 
             
              InvalidPageSize            = Class.new(ArgumentError)
         | 
| 9 | 
            +
              EventDuplicatedInStream    = Class.new(StandardError)
         | 
| 9 10 |  | 
| 10 11 | 
             
              class InvalidHandler < StandardError
         | 
| 11 | 
            -
                def initialize( | 
| 12 | 
            -
                  super("#call method not found in #{ | 
| 12 | 
            +
                def initialize(object)
         | 
| 13 | 
            +
                  super("#call method not found in #{object.inspect} subscriber. Are you sure it is a valid subscriber?")
         | 
| 13 14 | 
             
                end
         | 
| 14 15 | 
             
              end
         | 
| 15 16 | 
             
            end
         | 
| @@ -27,6 +27,17 @@ module RubyEventStore | |
| 27 27 | 
             
                    other_event.data.eql?(data)
         | 
| 28 28 | 
             
                end
         | 
| 29 29 |  | 
| 30 | 
            +
                BIG_VALUE = 0b111111100100000010010010110011101011000101010101001100100110000
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                # We don't use metadata because == does not use metadata
         | 
| 33 | 
            +
                def hash
         | 
| 34 | 
            +
                  [
         | 
| 35 | 
            +
                    self.class,
         | 
| 36 | 
            +
                    event_id,
         | 
| 37 | 
            +
                    data
         | 
| 38 | 
            +
                  ].hash ^ BIG_VALUE
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 30 41 | 
             
                alias_method :eql?, :==
         | 
| 31 42 | 
             
              end
         | 
| 32 | 
            -
            end
         | 
| 43 | 
            +
            end
         | 
| @@ -1,24 +1,34 @@ | |
| 1 1 | 
             
            require 'ostruct'
         | 
| 2 | 
            +
            require 'thread'
         | 
| 2 3 |  | 
| 3 4 | 
             
            module RubyEventStore
         | 
| 4 5 | 
             
              class InMemoryRepository
         | 
| 5 6 | 
             
                def initialize
         | 
| 6 7 | 
             
                  @all = Array.new
         | 
| 7 8 | 
             
                  @streams = Hash.new
         | 
| 9 | 
            +
                  @mutex = Mutex.new
         | 
| 8 10 | 
             
                end
         | 
| 9 11 |  | 
| 10 | 
            -
                def  | 
| 12 | 
            +
                def append_to_stream(events, stream_name, expected_version)
         | 
| 13 | 
            +
                  raise InvalidExpectedVersion if !expected_version.equal?(:any) && stream_name.eql?(GLOBAL_STREAM)
         | 
| 14 | 
            +
                  events = normalize_to_array(events)
         | 
| 11 15 | 
             
                  stream = read_stream_events_forward(stream_name)
         | 
| 12 | 
            -
                   | 
| 13 | 
            -
             | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            +
                  expected_version = case expected_version
         | 
| 17 | 
            +
                    when :none
         | 
| 18 | 
            +
                      -1
         | 
| 19 | 
            +
                    when :auto, :any
         | 
| 20 | 
            +
                      stream.size - 1
         | 
| 21 | 
            +
                    when Integer
         | 
| 22 | 
            +
                      expected_version
         | 
| 23 | 
            +
                    else
         | 
| 24 | 
            +
                      raise InvalidExpectedVersion
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
                  append_with_synchronize(events, expected_version, stream, stream_name)
         | 
| 27 | 
            +
                  self
         | 
| 16 28 | 
             
                end
         | 
| 17 29 |  | 
| 18 30 | 
             
                def delete_stream(stream_name)
         | 
| 19 | 
            -
                  removed = read_stream_events_forward(stream_name).map(&:event_id)
         | 
| 20 31 | 
             
                  streams.delete(stream_name)
         | 
| 21 | 
            -
                  all.delete_if{|ev| removed.include?(ev.event_id)}
         | 
| 22 32 | 
             
                end
         | 
| 23 33 |  | 
| 24 34 | 
             
                def has_event?(event_id)
         | 
| @@ -58,6 +68,34 @@ module RubyEventStore | |
| 58 68 | 
             
                private
         | 
| 59 69 | 
             
                attr_accessor :streams, :all
         | 
| 60 70 |  | 
| 71 | 
            +
                def normalize_to_array(events)
         | 
| 72 | 
            +
                  return *events
         | 
| 73 | 
            +
                end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                def append_with_synchronize(events, expected_version, stream, stream_name)
         | 
| 76 | 
            +
                  # expected_version :auto assumes external lock is used
         | 
| 77 | 
            +
                  # which makes reading stream before writing safe.
         | 
| 78 | 
            +
                  #
         | 
| 79 | 
            +
                  # To emulate potential concurrency issues of :auto strategy without
         | 
| 80 | 
            +
                  # such external lock we use Thread.pass to make race
         | 
| 81 | 
            +
                  # conditions more likely. And we only use mutex.synchronize for writing
         | 
| 82 | 
            +
                  # not for the whole read+write algorithm.
         | 
| 83 | 
            +
                  Thread.pass
         | 
| 84 | 
            +
                  @mutex.synchronize do
         | 
| 85 | 
            +
                    append(events, expected_version, stream, stream_name)
         | 
| 86 | 
            +
                  end
         | 
| 87 | 
            +
                end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                def append(events, expected_version, stream, stream_name)
         | 
| 90 | 
            +
                  raise WrongExpectedEventVersion unless (stream.size - 1).equal?(expected_version)
         | 
| 91 | 
            +
                  events.each do |event|
         | 
| 92 | 
            +
                    all.push(event)
         | 
| 93 | 
            +
                    raise EventDuplicatedInStream if stream.any?{|ev| ev.event_id.eql?(event.event_id) }
         | 
| 94 | 
            +
                    stream.push(event)
         | 
| 95 | 
            +
                  end
         | 
| 96 | 
            +
                  streams[stream_name] = stream
         | 
| 97 | 
            +
                end
         | 
| 98 | 
            +
             | 
| 61 99 | 
             
                def read_batch(source, start_event_id, count)
         | 
| 62 100 | 
             
                  return source[0..count-1] if start_event_id.equal?(:head)
         | 
| 63 101 | 
             
                  start_index = index_of(source, start_event_id)
         | 
| @@ -27,16 +27,12 @@ module RubyEventStore | |
| 27 27 | 
             
                    end
         | 
| 28 28 | 
             
                  end
         | 
| 29 29 |  | 
| 30 | 
            -
                  def proxy_for(klass)
         | 
| 31 | 
            -
                    dispatcher.proxy_for(klass)
         | 
| 32 | 
            -
                  end
         | 
| 33 | 
            -
             | 
| 34 30 | 
             
                  private
         | 
| 35 31 | 
             
                  attr_reader :subscribers, :dispatcher
         | 
| 36 32 |  | 
| 37 33 | 
             
                  def verify_subscriber(subscriber)
         | 
| 38 34 | 
             
                    raise SubscriberNotExist if subscriber.nil?
         | 
| 39 | 
            -
                     | 
| 35 | 
            +
                    dispatcher.verify(subscriber)
         | 
| 40 36 | 
             
                  end
         | 
| 41 37 |  | 
| 42 38 | 
             
                  def subscribe(subscriber, event_types)
         | 
| @@ -1,14 +1,25 @@ | |
| 1 1 | 
             
            module RubyEventStore
         | 
| 2 2 | 
             
              module PubSub
         | 
| 3 | 
            +
             | 
| 3 4 | 
             
                class Dispatcher
         | 
| 4 5 | 
             
                  def call(subscriber, event)
         | 
| 6 | 
            +
                    subscriber = subscriber.new if Class === subscriber
         | 
| 5 7 | 
             
                    subscriber.call(event)
         | 
| 6 8 | 
             
                  end
         | 
| 7 9 |  | 
| 8 | 
            -
                  def  | 
| 9 | 
            -
                     | 
| 10 | 
            -
                     | 
| 10 | 
            +
                  def verify(subscriber)
         | 
| 11 | 
            +
                    subscriber = klassify(subscriber)
         | 
| 12 | 
            +
                    subscriber.respond_to?(:call) or raise InvalidHandler.new(subscriber)
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  private
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def klassify(subscriber)
         | 
| 18 | 
            +
                    Class === subscriber ? subscriber.new : subscriber
         | 
| 19 | 
            +
                  rescue ArgumentError
         | 
| 20 | 
            +
                    raise InvalidHandler.new(subscriber)
         | 
| 11 21 | 
             
                  end
         | 
| 12 22 | 
             
                end
         | 
| 23 | 
            +
             | 
| 13 24 | 
             
              end
         | 
| 14 25 | 
             
            end
         | 
| @@ -1,30 +1,34 @@ | |
| 1 1 | 
             
            RSpec.shared_examples :dispatcher do |dispatcher|
         | 
| 2 | 
            -
              specify "calls subscribed  | 
| 3 | 
            -
                handler =  | 
| 2 | 
            +
              specify "calls subscribed instance" do
         | 
| 3 | 
            +
                handler = HandlerClass.new
         | 
| 4 4 | 
             
                event   = instance_double(::RubyEventStore::Event)
         | 
| 5 5 |  | 
| 6 6 | 
             
                expect(handler).to receive(:call).with(event)
         | 
| 7 7 | 
             
                dispatcher.(handler, event)
         | 
| 8 8 | 
             
              end
         | 
| 9 9 |  | 
| 10 | 
            -
              specify " | 
| 10 | 
            +
              specify "calls subscribed class" do
         | 
| 11 11 | 
             
                event   = instance_double(::RubyEventStore::Event)
         | 
| 12 12 |  | 
| 13 | 
            -
                 | 
| 14 | 
            -
                expect( | 
| 15 | 
            -
                dispatcher.( | 
| 16 | 
            -
                expect(HandlerClass.received).to eq(event)
         | 
| 13 | 
            +
                expect(HandlerClass).to receive(:new).and_return( h = HandlerClass.new )
         | 
| 14 | 
            +
                expect(h).to receive(:call).with(event)
         | 
| 15 | 
            +
                dispatcher.(HandlerClass, event)
         | 
| 17 16 | 
             
              end
         | 
| 18 17 |  | 
| 19 | 
            -
              specify " | 
| 20 | 
            -
                 | 
| 21 | 
            -
                   | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 18 | 
            +
              specify "allows callable classes and instances" do
         | 
| 19 | 
            +
                expect do
         | 
| 20 | 
            +
                  dispatcher.verify(HandlerClass)
         | 
| 21 | 
            +
                end.not_to raise_error
         | 
| 22 | 
            +
                expect do
         | 
| 23 | 
            +
                  dispatcher.verify(HandlerClass.new)
         | 
| 24 | 
            +
                end.not_to raise_error
         | 
| 25 | 
            +
                expect do
         | 
| 26 | 
            +
                  dispatcher.verify(Proc.new{ "yo" })
         | 
| 27 | 
            +
                end.not_to raise_error
         | 
| 25 28 | 
             
              end
         | 
| 26 29 |  | 
| 27 30 | 
             
              private
         | 
| 31 | 
            +
             | 
| 28 32 | 
             
              class HandlerClass
         | 
| 29 33 | 
             
                @@received = nil
         | 
| 30 34 | 
             
                def self.received
         | 
| @@ -23,6 +23,9 @@ RSpec.shared_examples :event_broker do |broker_class| | |
| 23 23 | 
             
                  @dispatched = []
         | 
| 24 24 | 
             
                end
         | 
| 25 25 |  | 
| 26 | 
            +
                def verify(_subscriber)
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 26 29 | 
             
                def call(subscriber, event)
         | 
| 27 30 | 
             
                  @dispatched << {subscriber: subscriber, event: event}
         | 
| 28 31 | 
             
                end
         | 
| @@ -58,19 +61,17 @@ RSpec.shared_examples :event_broker do |broker_class| | |
| 58 61 | 
             
              end
         | 
| 59 62 |  | 
| 60 63 | 
             
              it 'raises error when no valid method on handler' do
         | 
| 61 | 
            -
                message = "#call method not found " +
         | 
| 62 | 
            -
                          "in InvalidTestHandler subscriber." +
         | 
| 63 | 
            -
                          " Are you sure it is a valid subscriber?"
         | 
| 64 64 | 
             
                subscriber = InvalidTestHandler.new
         | 
| 65 | 
            -
                expect  | 
| 65 | 
            +
                expect do
         | 
| 66 | 
            +
                  broker.add_subscriber(subscriber, [Test1DomainEvent])
         | 
| 67 | 
            +
                end.to raise_error(RubyEventStore::InvalidHandler)
         | 
| 66 68 | 
             
              end
         | 
| 67 69 |  | 
| 68 70 | 
             
              it 'raises error when no valid method on global handler' do
         | 
| 69 | 
            -
                message = "#call method not found " +
         | 
| 70 | 
            -
                          "in InvalidTestHandler subscriber." +
         | 
| 71 | 
            -
                          " Are you sure it is a valid subscriber?"
         | 
| 72 71 | 
             
                subscriber = InvalidTestHandler.new
         | 
| 73 | 
            -
                expect  | 
| 72 | 
            +
                expect do
         | 
| 73 | 
            +
                  broker.add_global_subscriber(subscriber)
         | 
| 74 | 
            +
                end.to raise_error(RubyEventStore::InvalidHandler)
         | 
| 74 75 | 
             
              end
         | 
| 75 76 |  | 
| 76 77 | 
             
              it 'returns lambda as an output of global subscribe methods' do
         | 
| @@ -121,19 +122,6 @@ RSpec.shared_examples :event_broker do |broker_class| | |
| 121 122 | 
             
                expect(dispatcher.dispatched).to eq([{subscriber: handler, event: event1}])
         | 
| 122 123 | 
             
              end
         | 
| 123 124 |  | 
| 124 | 
            -
              it "returns callable proxy" do
         | 
| 125 | 
            -
                proxy = broker.proxy_for(TestHandler)
         | 
| 126 | 
            -
                expect(proxy.respond_to?(:call)).to be_truthy
         | 
| 127 | 
            -
              end
         | 
| 128 | 
            -
             | 
| 129 | 
            -
              specify "fails to build proxy when no call method defined on class" do
         | 
| 130 | 
            -
                message = "#call method not found " +
         | 
| 131 | 
            -
                  "in InvalidTestHandler subscriber." +
         | 
| 132 | 
            -
                  " Are you sure it is a valid subscriber?"
         | 
| 133 | 
            -
             | 
| 134 | 
            -
                expect { broker.proxy_for(InvalidTestHandler) }.to raise_error(::RubyEventStore::InvalidHandler, message)
         | 
| 135 | 
            -
              end
         | 
| 136 | 
            -
             | 
| 137 125 | 
             
              private
         | 
| 138 126 | 
             
              class HandlerClass
         | 
| 139 127 | 
             
                @@received = nil
         | 
| @@ -6,15 +6,249 @@ RSpec.shared_examples :event_repository do |repository_class| | |
| 6 6 | 
             
                expect(repository.read_all_streams_forward(:head, 1)).to be_empty
         | 
| 7 7 | 
             
              end
         | 
| 8 8 |  | 
| 9 | 
            -
               | 
| 10 | 
            -
                 | 
| 11 | 
            -
             | 
| 9 | 
            +
              specify 'publish fail if expected version is nil' do
         | 
| 10 | 
            +
                expect do
         | 
| 11 | 
            +
                  repository.append_to_stream(event = TestDomainEvent.new, 'stream', nil)
         | 
| 12 | 
            +
                end.to raise_error(RubyEventStore::InvalidExpectedVersion)
         | 
| 12 13 | 
             
              end
         | 
| 13 14 |  | 
| 14 | 
            -
               | 
| 15 | 
            +
              specify 'append_to_stream returns self' do
         | 
| 16 | 
            +
                repository.
         | 
| 17 | 
            +
                  append_to_stream(event = TestDomainEvent.new, 'stream', -1).
         | 
| 18 | 
            +
                  append_to_stream(event = TestDomainEvent.new, 'stream', 0)
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
              specify 'adds initial event to a new stream' do
         | 
| 22 | 
            +
                repository.append_to_stream(event = TestDomainEvent.new, 'stream', :none)
         | 
| 23 | 
            +
                expect(repository.read_all_streams_forward(:head, 1).first).to eq(event)
         | 
| 24 | 
            +
                expect(repository.read_stream_events_forward('stream').first).to eq(event)
         | 
| 25 | 
            +
                expect(repository.read_stream_events_forward('other_stream')).to be_empty
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              specify 'adds multiple initial events to a new stream' do
         | 
| 29 | 
            +
                repository.append_to_stream([
         | 
| 30 | 
            +
                  event0 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 31 | 
            +
                  event1 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 32 | 
            +
                ], 'stream', :none)
         | 
| 33 | 
            +
                expect(repository.read_all_streams_forward(:head, 2)).to eq([event0, event1])
         | 
| 34 | 
            +
                expect(repository.read_stream_events_forward('stream')).to eq([event0, event1])
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              specify 'correct expected version on second write' do
         | 
| 38 | 
            +
                repository.append_to_stream([
         | 
| 39 | 
            +
                  event0 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 40 | 
            +
                  event1 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 41 | 
            +
                ], 'stream', :none)
         | 
| 42 | 
            +
                repository.append_to_stream([
         | 
| 43 | 
            +
                  event2 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 44 | 
            +
                  event3 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 45 | 
            +
                ], 'stream', 1)
         | 
| 46 | 
            +
                expect(repository.read_all_streams_forward(:head, 4)).to eq([event0, event1, event2, event3])
         | 
| 47 | 
            +
                expect(repository.read_stream_events_forward('stream')).to eq([event0, event1, event2, event3])
         | 
| 48 | 
            +
              end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
              specify 'incorrect expected version on second write' do
         | 
| 51 | 
            +
                repository.append_to_stream([
         | 
| 52 | 
            +
                  event0 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 53 | 
            +
                  event1 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 54 | 
            +
                ], 'stream', :none)
         | 
| 55 | 
            +
                expect do
         | 
| 56 | 
            +
                  repository.append_to_stream([
         | 
| 57 | 
            +
                    event2 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 58 | 
            +
                    event3 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 59 | 
            +
                  ], 'stream', 0)
         | 
| 60 | 
            +
                end.to raise_error(RubyEventStore::WrongExpectedEventVersion)
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                expect(repository.read_all_streams_forward(:head, 4)).to eq([event0, event1])
         | 
| 63 | 
            +
                expect(repository.read_stream_events_forward('stream')).to eq([event0, event1])
         | 
| 64 | 
            +
              end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
              specify ':none on first and subsequent write' do
         | 
| 67 | 
            +
                repository.append_to_stream([
         | 
| 68 | 
            +
                  eventA = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 69 | 
            +
                ], 'stream', :none)
         | 
| 70 | 
            +
                expect do
         | 
| 71 | 
            +
                  repository.append_to_stream([
         | 
| 72 | 
            +
                    eventB = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 73 | 
            +
                  ], 'stream', :none)
         | 
| 74 | 
            +
                end.to raise_error(RubyEventStore::WrongExpectedEventVersion)
         | 
| 75 | 
            +
                expect(repository.read_all_streams_forward(:head, 1)).to eq([eventA])
         | 
| 76 | 
            +
                expect(repository.read_stream_events_forward('stream')).to eq([eventA])
         | 
| 77 | 
            +
              end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
              specify ':any allows stream with best-effort order and no guarantee' do
         | 
| 80 | 
            +
                repository.append_to_stream([
         | 
| 81 | 
            +
                  event0 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 82 | 
            +
                  event1 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 83 | 
            +
                ], 'stream', :any)
         | 
| 84 | 
            +
                repository.append_to_stream([
         | 
| 85 | 
            +
                  event2 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 86 | 
            +
                  event3 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 87 | 
            +
                ], 'stream', :any)
         | 
| 88 | 
            +
                expect(repository.read_all_streams_forward(:head, 4).to_set).to eq(Set.new([event0, event1, event2, event3]))
         | 
| 89 | 
            +
                expect(repository.read_stream_events_forward('stream').to_set).to eq(Set.new([event0, event1, event2, event3]))
         | 
| 90 | 
            +
              end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
              specify ':auto queries for last position in given stream' do
         | 
| 93 | 
            +
                skip unless test_expected_version_auto
         | 
| 94 | 
            +
                repository.append_to_stream([
         | 
| 95 | 
            +
                  eventA = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 96 | 
            +
                  eventB = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 97 | 
            +
                  eventC = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 98 | 
            +
                ], 'another', :auto)
         | 
| 99 | 
            +
                repository.append_to_stream([
         | 
| 100 | 
            +
                  event0 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 101 | 
            +
                  event1 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 102 | 
            +
                ], 'stream', :auto)
         | 
| 103 | 
            +
                repository.append_to_stream([
         | 
| 104 | 
            +
                  event2 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 105 | 
            +
                  event3 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 106 | 
            +
                ], 'stream', 1)
         | 
| 107 | 
            +
              end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
              specify ':auto starts from 0' do
         | 
| 110 | 
            +
                skip unless test_expected_version_auto
         | 
| 111 | 
            +
                repository.append_to_stream([
         | 
| 112 | 
            +
                  event0 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 113 | 
            +
                ], 'stream', :auto)
         | 
| 114 | 
            +
                expect do
         | 
| 115 | 
            +
                  repository.append_to_stream([
         | 
| 116 | 
            +
                    event1 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 117 | 
            +
                  ], 'stream', -1)
         | 
| 118 | 
            +
                end.to raise_error(RubyEventStore::WrongExpectedEventVersion)
         | 
| 119 | 
            +
              end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
              specify ':auto queries for last position and follows in incremental way' do
         | 
| 122 | 
            +
                skip unless test_expected_version_auto
         | 
| 123 | 
            +
                # It is expected that there is higher level lock
         | 
| 124 | 
            +
                # So this query is safe from race conditions
         | 
| 125 | 
            +
                repository.append_to_stream([
         | 
| 126 | 
            +
                  event0 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 127 | 
            +
                  event1 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 128 | 
            +
                ], 'stream', :auto)
         | 
| 129 | 
            +
                repository.append_to_stream([
         | 
| 130 | 
            +
                  event2 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 131 | 
            +
                  event3 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 132 | 
            +
                ], 'stream', :auto)
         | 
| 133 | 
            +
                expect(repository.read_all_streams_forward(:head, 4)).to eq([
         | 
| 134 | 
            +
                  event0, event1,
         | 
| 135 | 
            +
                  event2, event3
         | 
| 136 | 
            +
                ])
         | 
| 137 | 
            +
                expect(repository.read_stream_events_forward('stream')).to eq([event0, event1, event2, event3])
         | 
| 138 | 
            +
              end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
              specify ':auto is compatible with manual expectation' do
         | 
| 141 | 
            +
                skip unless test_expected_version_auto
         | 
| 142 | 
            +
                repository.append_to_stream([
         | 
| 143 | 
            +
                  event0 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 144 | 
            +
                  event1 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 145 | 
            +
                ], 'stream', :auto)
         | 
| 146 | 
            +
                repository.append_to_stream([
         | 
| 147 | 
            +
                  event2 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 148 | 
            +
                  event3 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 149 | 
            +
                ], 'stream', 1)
         | 
| 150 | 
            +
                expect(repository.read_all_streams_forward(:head, 4)).to eq([event0, event1, event2, event3])
         | 
| 151 | 
            +
                expect(repository.read_stream_events_forward('stream')).to eq([event0, event1, event2, event3])
         | 
| 152 | 
            +
              end
         | 
| 153 | 
            +
             | 
| 154 | 
            +
              specify 'manual is compatible with auto expectation' do
         | 
| 155 | 
            +
                skip unless test_expected_version_auto
         | 
| 156 | 
            +
                repository.append_to_stream([
         | 
| 157 | 
            +
                  event0 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 158 | 
            +
                  event1 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 159 | 
            +
                ], 'stream', :none)
         | 
| 160 | 
            +
                repository.append_to_stream([
         | 
| 161 | 
            +
                  event2 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 162 | 
            +
                  event3 = TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 163 | 
            +
                ], 'stream', :auto)
         | 
| 164 | 
            +
                expect(repository.read_all_streams_forward(:head, 4)).to eq([event0, event1, event2, event3])
         | 
| 165 | 
            +
                expect(repository.read_stream_events_forward('stream')).to eq([event0, event1, event2, event3])
         | 
| 166 | 
            +
              end
         | 
| 167 | 
            +
             | 
| 168 | 
            +
              specify 'unlimited concurrency for :any - everything should succeed' do
         | 
| 169 | 
            +
                skip unless test_race_conditions_any
         | 
| 170 | 
            +
                verify_conncurency_assumptions
         | 
| 171 | 
            +
                begin
         | 
| 172 | 
            +
                  concurrency_level = 4
         | 
| 173 | 
            +
             | 
| 174 | 
            +
                  fail_occurred = false
         | 
| 175 | 
            +
                  wait_for_it  = true
         | 
| 176 | 
            +
             | 
| 177 | 
            +
                  threads = concurrency_level.times.map do |i|
         | 
| 178 | 
            +
                    Thread.new do
         | 
| 179 | 
            +
                      true while wait_for_it
         | 
| 180 | 
            +
                      begin
         | 
| 181 | 
            +
                        100.times do |j|
         | 
| 182 | 
            +
                          eid = "0000000#{i}-#{sprintf("%04d", j)}-0000-0000-000000000000"
         | 
| 183 | 
            +
                          repository.append_to_stream([
         | 
| 184 | 
            +
                            TestDomainEvent.new(event_id: eid),
         | 
| 185 | 
            +
                          ], 'stream', :any)
         | 
| 186 | 
            +
                        end
         | 
| 187 | 
            +
                      rescue RubyEventStore::WrongExpectedEventVersion
         | 
| 188 | 
            +
                        fail_occurred = true
         | 
| 189 | 
            +
                      end
         | 
| 190 | 
            +
                    end
         | 
| 191 | 
            +
                  end
         | 
| 192 | 
            +
                  wait_for_it = false
         | 
| 193 | 
            +
                  threads.each(&:join)
         | 
| 194 | 
            +
                  expect(fail_occurred).to eq(false)
         | 
| 195 | 
            +
                  expect(repository.read_stream_events_forward('stream').size).to eq(400)
         | 
| 196 | 
            +
                  events_in_stream = repository.read_stream_events_forward('stream')
         | 
| 197 | 
            +
                  expect(events_in_stream.size).to eq(400)
         | 
| 198 | 
            +
                  events0 = events_in_stream.select do |ev|
         | 
| 199 | 
            +
                    ev.event_id.start_with?("0-")
         | 
| 200 | 
            +
                  end
         | 
| 201 | 
            +
                  expect(events0).to eq(events0.sort_by{|ev| ev.event_id })
         | 
| 202 | 
            +
                ensure
         | 
| 203 | 
            +
                  cleanup_concurrency_test
         | 
| 204 | 
            +
                end
         | 
| 205 | 
            +
              end
         | 
| 206 | 
            +
             | 
| 207 | 
            +
              specify 'limited concurrency for :auto - some operations will fail without outside lock, stream is ordered' do
         | 
| 208 | 
            +
                skip unless test_expected_version_auto
         | 
| 209 | 
            +
                skip unless test_race_conditions_auto
         | 
| 210 | 
            +
                verify_conncurency_assumptions
         | 
| 211 | 
            +
                begin
         | 
| 212 | 
            +
                  concurrency_level = 4
         | 
| 213 | 
            +
             | 
| 214 | 
            +
                  fail_occurred = 0
         | 
| 215 | 
            +
                  wait_for_it  = true
         | 
| 216 | 
            +
             | 
| 217 | 
            +
                  threads = concurrency_level.times.map do |i|
         | 
| 218 | 
            +
                    Thread.new do
         | 
| 219 | 
            +
                      true while wait_for_it
         | 
| 220 | 
            +
                      100.times do |j|
         | 
| 221 | 
            +
                        begin
         | 
| 222 | 
            +
                          eid = "0000000#{i}-#{sprintf("%04d", j)}-0000-0000-000000000000"
         | 
| 223 | 
            +
                          repository.append_to_stream([
         | 
| 224 | 
            +
                            TestDomainEvent.new(event_id: eid),
         | 
| 225 | 
            +
                          ], 'stream', :auto)
         | 
| 226 | 
            +
                          sleep(rand(concurrency_level) / 1000.0)
         | 
| 227 | 
            +
                        rescue RubyEventStore::WrongExpectedEventVersion
         | 
| 228 | 
            +
                          fail_occurred +=1
         | 
| 229 | 
            +
                        end
         | 
| 230 | 
            +
                      end
         | 
| 231 | 
            +
                    end
         | 
| 232 | 
            +
                  end
         | 
| 233 | 
            +
                  wait_for_it = false
         | 
| 234 | 
            +
                  threads.each(&:join)
         | 
| 235 | 
            +
                  expect(fail_occurred).to be > 0
         | 
| 236 | 
            +
                  events_in_stream = repository.read_stream_events_forward('stream')
         | 
| 237 | 
            +
                  expect(events_in_stream.size).to be < 400
         | 
| 238 | 
            +
                  expect(events_in_stream.size).to be >= 100
         | 
| 239 | 
            +
                  events0 = events_in_stream.select do |ev|
         | 
| 240 | 
            +
                    ev.event_id.start_with?("0-")
         | 
| 241 | 
            +
                  end
         | 
| 242 | 
            +
                  expect(events0).to eq(events0.sort_by{|ev| ev.event_id })
         | 
| 243 | 
            +
                  additional_limited_concurrency_for_auto_check
         | 
| 244 | 
            +
                ensure
         | 
| 245 | 
            +
                  cleanup_concurrency_test
         | 
| 246 | 
            +
                end
         | 
| 247 | 
            +
              end
         | 
| 248 | 
            +
             | 
| 249 | 
            +
              it 'appended event is stored in given stream' do
         | 
| 15 250 | 
             
                expected_event = TestDomainEvent.new(data: {})
         | 
| 16 | 
            -
                 | 
| 17 | 
            -
                expect(created).to eq(expected_event)
         | 
| 251 | 
            +
                repository.append_to_stream(expected_event, 'stream', :any)
         | 
| 18 252 | 
             
                expect(repository.read_all_streams_forward(:head, 1).first).to eq(expected_event)
         | 
| 19 253 | 
             
                expect(repository.read_stream_events_forward('stream').first).to eq(expected_event)
         | 
| 20 254 | 
             
                expect(repository.read_stream_events_forward('other_stream')).to be_empty
         | 
| @@ -22,92 +256,180 @@ RSpec.shared_examples :event_repository do |repository_class| | |
| 22 256 |  | 
| 23 257 | 
             
              it 'data attributes are retrieved' do
         | 
| 24 258 | 
             
                event = TestDomainEvent.new(data: { order_id: 3 })
         | 
| 25 | 
            -
                repository. | 
| 259 | 
            +
                repository.append_to_stream(event, 'stream', :any)
         | 
| 26 260 | 
             
                retrieved_event = repository.read_all_streams_forward(:head, 1).first
         | 
| 27 261 | 
             
                expect(retrieved_event.data[:order_id]).to eq(3)
         | 
| 28 262 | 
             
              end
         | 
| 29 263 |  | 
| 30 264 | 
             
              it 'metadata attributes are retrieved' do
         | 
| 31 265 | 
             
                event = TestDomainEvent.new(metadata: { request_id: 3 })
         | 
| 32 | 
            -
                repository. | 
| 266 | 
            +
                repository.append_to_stream(event, 'stream', :any)
         | 
| 33 267 | 
             
                retrieved_event = repository.read_all_streams_forward(:head, 1).first
         | 
| 34 268 | 
             
                expect(retrieved_event.metadata[:request_id]).to eq(3)
         | 
| 35 269 | 
             
              end
         | 
| 36 270 |  | 
| 37 271 | 
             
              it 'does not have deleted streams' do
         | 
| 38 | 
            -
                repository. | 
| 39 | 
            -
                repository. | 
| 40 | 
            -
             | 
| 41 | 
            -
                expect(repository.read_stream_events_forward('stream').count).to eq 1
         | 
| 42 | 
            -
                expect(repository.read_stream_events_forward('other_stream').count).to eq 1
         | 
| 43 | 
            -
                expect(repository.read_all_streams_forward(:head, 10).count).to eq 2
         | 
| 272 | 
            +
                repository.append_to_stream(e1 = TestDomainEvent.new, 'stream', -1)
         | 
| 273 | 
            +
                repository.append_to_stream(e2 = TestDomainEvent.new, 'other_stream', -1)
         | 
| 44 274 |  | 
| 45 275 | 
             
                repository.delete_stream('stream')
         | 
| 46 276 | 
             
                expect(repository.read_stream_events_forward('stream')).to be_empty
         | 
| 47 | 
            -
                expect(repository.read_stream_events_forward('other_stream') | 
| 48 | 
            -
                expect(repository.read_all_streams_forward(:head, 10) | 
| 277 | 
            +
                expect(repository.read_stream_events_forward('other_stream')).to eq([e2])
         | 
| 278 | 
            +
                expect(repository.read_all_streams_forward(:head, 10)).to eq([e1,e2])
         | 
| 49 279 | 
             
              end
         | 
| 50 280 |  | 
| 51 281 | 
             
              it 'has or has not domain event' do
         | 
| 52 | 
            -
                 | 
| 282 | 
            +
                just_an_id = 'd5c134c2-db65-4e87-b6ea-d196f8f1a292'
         | 
| 283 | 
            +
                repository.append_to_stream(TestDomainEvent.new(event_id: just_an_id), 'stream', -1)
         | 
| 53 284 |  | 
| 54 | 
            -
                expect(repository.has_event?( | 
| 285 | 
            +
                expect(repository.has_event?(just_an_id)).to be_truthy
         | 
| 286 | 
            +
                expect(repository.has_event?(just_an_id.clone)).to be_truthy
         | 
| 55 287 | 
             
                expect(repository.has_event?('any other id')).to be_falsey
         | 
| 56 288 | 
             
              end
         | 
| 57 289 |  | 
| 58 290 | 
             
              it 'knows last event in stream' do
         | 
| 59 | 
            -
                repository. | 
| 60 | 
            -
                repository. | 
| 291 | 
            +
                repository.append_to_stream(TestDomainEvent.new(event_id: '00000000-0000-0000-0000-000000000001'), 'stream', -1)
         | 
| 292 | 
            +
                repository.append_to_stream(TestDomainEvent.new(event_id: '00000000-0000-0000-0000-000000000002'), 'stream', 0)
         | 
| 61 293 |  | 
| 62 | 
            -
                expect(repository.last_stream_event('stream')).to eq(TestDomainEvent.new(event_id: ' | 
| 294 | 
            +
                expect(repository.last_stream_event('stream')).to eq(TestDomainEvent.new(event_id: '00000000-0000-0000-0000-000000000002'))
         | 
| 63 295 | 
             
                expect(repository.last_stream_event('other_stream')).to be_nil
         | 
| 64 296 | 
             
              end
         | 
| 65 297 |  | 
| 66 298 | 
             
              it 'reads batch of events from stream forward & backward' do
         | 
| 67 | 
            -
                event_ids =  | 
| 68 | 
            -
                 | 
| 69 | 
            -
                 | 
| 70 | 
            -
             | 
| 299 | 
            +
                event_ids = ["96c920b1-cdd0-40f4-907c-861b9fff7d02", "56404f79-0ba0-4aa0-8524-dc3436368ca0", "6a54dd21-f9d8-4857-a195-f5588d9e406c", "0e50a9cd-f981-4e39-93d5-697fc7285b98", "d85589bc-b993-41d4-812f-fc631d9185d5", "96bdacda-77dd-4d7d-973d-cbdaa5842855", "94688199-e6b7-4180-bf8e-825b6808e6cc", "68fab040-741e-4bc2-9cca-5b8855b0ca19", "ab60114c-011d-4d58-ab31-7ba65d99975e", "868cac42-3d19-4b39-84e8-cd32d65c2445"]
         | 
| 300 | 
            +
                events = event_ids.map{|id| TestDomainEvent.new(event_id: id) }
         | 
| 301 | 
            +
                repository.append_to_stream(TestDomainEvent.new, 'other_stream', -1)
         | 
| 302 | 
            +
                events.each.with_index do |event, index|
         | 
| 303 | 
            +
                  repository.append_to_stream(event, 'stream', index - 1)
         | 
| 71 304 | 
             
                end
         | 
| 72 | 
            -
                repository. | 
| 305 | 
            +
                repository.append_to_stream(TestDomainEvent.new, 'other_stream', 0)
         | 
| 73 306 |  | 
| 74 | 
            -
                expect(repository.read_events_forward('stream', :head, 3)).to eq | 
| 75 | 
            -
                expect(repository.read_events_forward('stream', :head, 100)).to eq | 
| 76 | 
            -
                expect(repository.read_events_forward('stream',  | 
| 77 | 
            -
                expect(repository.read_events_forward('stream',  | 
| 307 | 
            +
                expect(repository.read_events_forward('stream', :head, 3)).to eq(events.first(3))
         | 
| 308 | 
            +
                expect(repository.read_events_forward('stream', :head, 100)).to eq(events)
         | 
| 309 | 
            +
                expect(repository.read_events_forward('stream', events[4].event_id, 4)).to eq(events[5..8])
         | 
| 310 | 
            +
                expect(repository.read_events_forward('stream', events[4].event_id, 100)).to eq(events[5..9])
         | 
| 78 311 |  | 
| 79 | 
            -
                expect(repository.read_events_backward('stream', :head, 3)).to eq | 
| 80 | 
            -
                expect(repository.read_events_backward('stream', :head, 100)).to eq | 
| 81 | 
            -
                expect(repository.read_events_backward('stream',  | 
| 82 | 
            -
                expect(repository.read_events_backward('stream',  | 
| 312 | 
            +
                expect(repository.read_events_backward('stream', :head, 3)).to eq(events.last(3).reverse)
         | 
| 313 | 
            +
                expect(repository.read_events_backward('stream', :head, 100)).to eq(events.reverse)
         | 
| 314 | 
            +
                expect(repository.read_events_backward('stream', events[4].event_id, 4)).to eq(events.first(4).reverse)
         | 
| 315 | 
            +
                expect(repository.read_events_backward('stream', events[4].event_id, 100)).to eq(events.first(4).reverse)
         | 
| 83 316 | 
             
              end
         | 
| 84 317 |  | 
| 85 318 |  | 
| 86 319 | 
             
              it 'reads all stream events forward & backward' do
         | 
| 87 | 
            -
                 | 
| 88 | 
            -
                 | 
| 89 | 
            -
                repository. | 
| 90 | 
            -
                repository. | 
| 91 | 
            -
                repository. | 
| 320 | 
            +
                s1 = 'stream'
         | 
| 321 | 
            +
                s2 = 'other_stream'
         | 
| 322 | 
            +
                repository.append_to_stream(a = TestDomainEvent.new(event_id: '7010d298-ab69-4bb1-9251-f3466b5d1282'), s1, -1)
         | 
| 323 | 
            +
                repository.append_to_stream(b = TestDomainEvent.new(event_id: '34f88aca-aaba-4ca0-9256-8017b47528c5'), s2, -1)
         | 
| 324 | 
            +
                repository.append_to_stream(c = TestDomainEvent.new(event_id: '8e61c864-ceae-4684-8726-97c34eb8fc4f'), s1, 0)
         | 
| 325 | 
            +
                repository.append_to_stream(d = TestDomainEvent.new(event_id: '30963ed9-6349-450b-ac9b-8ea50115b3bd'), s2, 0)
         | 
| 326 | 
            +
                repository.append_to_stream(e = TestDomainEvent.new(event_id: '5bdc58b7-e8a7-4621-afd6-ccb828d72457'), s2, 1)
         | 
| 92 327 |  | 
| 93 | 
            -
                expect(repository.read_stream_events_forward( | 
| 94 | 
            -
                expect(repository.read_stream_events_backward( | 
| 328 | 
            +
                expect(repository.read_stream_events_forward(s1)).to eq [a,c]
         | 
| 329 | 
            +
                expect(repository.read_stream_events_backward(s1)).to eq [c,a]
         | 
| 95 330 | 
             
              end
         | 
| 96 331 |  | 
| 97 332 | 
             
              it 'reads batch of events from all streams forward & backward' do
         | 
| 98 | 
            -
                event_ids =  | 
| 99 | 
            -
                event_ids. | 
| 100 | 
            -
             | 
| 333 | 
            +
                event_ids = ["96c920b1-cdd0-40f4-907c-861b9fff7d02", "56404f79-0ba0-4aa0-8524-dc3436368ca0", "6a54dd21-f9d8-4857-a195-f5588d9e406c", "0e50a9cd-f981-4e39-93d5-697fc7285b98", "d85589bc-b993-41d4-812f-fc631d9185d5", "96bdacda-77dd-4d7d-973d-cbdaa5842855", "94688199-e6b7-4180-bf8e-825b6808e6cc", "68fab040-741e-4bc2-9cca-5b8855b0ca19", "ab60114c-011d-4d58-ab31-7ba65d99975e", "868cac42-3d19-4b39-84e8-cd32d65c2445"]
         | 
| 334 | 
            +
                events = event_ids.map{|id| TestDomainEvent.new(event_id: id) }
         | 
| 335 | 
            +
                events.each do |ev|
         | 
| 336 | 
            +
                  repository.append_to_stream(ev, SecureRandom.uuid, -1)
         | 
| 101 337 | 
             
                end
         | 
| 102 338 |  | 
| 103 | 
            -
                expect(repository.read_all_streams_forward(:head, 3)).to eq | 
| 104 | 
            -
                expect(repository.read_all_streams_forward(:head, 100)).to eq | 
| 105 | 
            -
                expect(repository.read_all_streams_forward( | 
| 106 | 
            -
                expect(repository.read_all_streams_forward( | 
| 339 | 
            +
                expect(repository.read_all_streams_forward(:head, 3)).to eq(events.first(3))
         | 
| 340 | 
            +
                expect(repository.read_all_streams_forward(:head, 100)).to eq(events)
         | 
| 341 | 
            +
                expect(repository.read_all_streams_forward(events[4].event_id, 4)).to eq(events[5..8])
         | 
| 342 | 
            +
                expect(repository.read_all_streams_forward(events[4].event_id, 100)).to eq(events[5..9])
         | 
| 343 | 
            +
             | 
| 344 | 
            +
                expect(repository.read_all_streams_backward(:head, 3)).to eq(events.last(3).reverse)
         | 
| 345 | 
            +
                expect(repository.read_all_streams_backward(:head, 100)).to eq(events.reverse)
         | 
| 346 | 
            +
                expect(repository.read_all_streams_backward(events[4].event_id, 4)).to eq(events.first(4).reverse)
         | 
| 347 | 
            +
                expect(repository.read_all_streams_backward(events[4].event_id, 100)).to eq(events.first(4).reverse)
         | 
| 348 | 
            +
              end
         | 
| 349 | 
            +
             | 
| 350 | 
            +
              it 'reads events different uuid object but same content' do
         | 
| 351 | 
            +
                event_ids = [
         | 
| 352 | 
            +
                  "96c920b1-cdd0-40f4-907c-861b9fff7d02",
         | 
| 353 | 
            +
                  "56404f79-0ba0-4aa0-8524-dc3436368ca0"
         | 
| 354 | 
            +
                ]
         | 
| 355 | 
            +
                events = event_ids.map{|id| TestDomainEvent.new(event_id: id) }
         | 
| 356 | 
            +
                repository.append_to_stream(events.first, 'stream', -1)
         | 
| 357 | 
            +
                repository.append_to_stream(events.last,  'stream',  0)
         | 
| 358 | 
            +
             | 
| 359 | 
            +
                expect(repository.read_all_streams_forward("96c920b1-cdd0-40f4-907c-861b9fff7d02", 1)).to eq([events.last])
         | 
| 360 | 
            +
                expect(repository.read_all_streams_backward("56404f79-0ba0-4aa0-8524-dc3436368ca0", 1)).to eq([events.first])
         | 
| 361 | 
            +
             | 
| 362 | 
            +
                expect(repository.read_events_forward('stream', "96c920b1-cdd0-40f4-907c-861b9fff7d02", 1)).to eq([events.last])
         | 
| 363 | 
            +
                expect(repository.read_events_backward('stream', "56404f79-0ba0-4aa0-8524-dc3436368ca0", 1)).to eq([events.first])
         | 
| 364 | 
            +
              end
         | 
| 365 | 
            +
             | 
| 366 | 
            +
              it 'does not allow same event twice in a stream' do
         | 
| 367 | 
            +
                repository.append_to_stream(
         | 
| 368 | 
            +
                  TestDomainEvent.new(event_id: "a1b49edb-7636-416f-874a-88f94b859bef"),
         | 
| 369 | 
            +
                  'stream',
         | 
| 370 | 
            +
                  -1
         | 
| 371 | 
            +
                )
         | 
| 372 | 
            +
                expect do
         | 
| 373 | 
            +
                  repository.append_to_stream(
         | 
| 374 | 
            +
                    TestDomainEvent.new(event_id: "a1b49edb-7636-416f-874a-88f94b859bef"),
         | 
| 375 | 
            +
                    'stream',
         | 
| 376 | 
            +
                    0
         | 
| 377 | 
            +
                  )
         | 
| 378 | 
            +
                end.to raise_error(RubyEventStore::EventDuplicatedInStream)
         | 
| 379 | 
            +
              end
         | 
| 380 | 
            +
             | 
| 381 | 
            +
              it 'allows appending to GLOBAL_STREAM explicitly' do
         | 
| 382 | 
            +
                event = TestDomainEvent.new(event_id: "df8b2ba3-4e2c-4888-8d14-4364855fa80e")
         | 
| 383 | 
            +
                repository.append_to_stream(event, "all", :any)
         | 
| 384 | 
            +
             | 
| 385 | 
            +
                expect(repository.read_all_streams_forward(:head, 10)).to eq([event])
         | 
| 386 | 
            +
              end
         | 
| 387 | 
            +
             | 
| 388 | 
            +
              specify 'GLOBAL_STREAM is unordered, one cannot expect specific version number to work' do
         | 
| 389 | 
            +
                expect {
         | 
| 390 | 
            +
                  event = TestDomainEvent.new(event_id: "df8b2ba3-4e2c-4888-8d14-4364855fa80e")
         | 
| 391 | 
            +
                  repository.append_to_stream(event, "all", 42)
         | 
| 392 | 
            +
                }.to raise_error(RubyEventStore::InvalidExpectedVersion)
         | 
| 393 | 
            +
              end
         | 
| 394 | 
            +
             | 
| 395 | 
            +
              specify 'GLOBAL_STREAM is unordered, one cannot expect :none to work' do
         | 
| 396 | 
            +
                expect {
         | 
| 397 | 
            +
                  event = TestDomainEvent.new(event_id: "df8b2ba3-4e2c-4888-8d14-4364855fa80e")
         | 
| 398 | 
            +
                  repository.append_to_stream(event, "all", :none)
         | 
| 399 | 
            +
                }.to raise_error(RubyEventStore::InvalidExpectedVersion)
         | 
| 400 | 
            +
              end
         | 
| 401 | 
            +
             | 
| 402 | 
            +
              specify 'GLOBAL_STREAM is unordered, one cannot expect :auto to work' do
         | 
| 403 | 
            +
                expect {
         | 
| 404 | 
            +
                  event = TestDomainEvent.new(event_id: "df8b2ba3-4e2c-4888-8d14-4364855fa80e")
         | 
| 405 | 
            +
                  repository.append_to_stream(event, "all", :auto)
         | 
| 406 | 
            +
                }.to raise_error(RubyEventStore::InvalidExpectedVersion)
         | 
| 407 | 
            +
              end
         | 
| 408 | 
            +
             | 
| 409 | 
            +
              specify "only :none, :any, :auto and Integer allowed as expected_version" do
         | 
| 410 | 
            +
                [Object.new, SecureRandom.uuid, :foo].each do |invalid_expected_version|
         | 
| 411 | 
            +
                  expect {
         | 
| 412 | 
            +
                    repository.append_to_stream(
         | 
| 413 | 
            +
                      TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 414 | 
            +
                      'some_stream',
         | 
| 415 | 
            +
                      invalid_expected_version
         | 
| 416 | 
            +
                    )
         | 
| 417 | 
            +
                  }.to raise_error(RubyEventStore::InvalidExpectedVersion)
         | 
| 418 | 
            +
                end
         | 
| 419 | 
            +
              end
         | 
| 420 | 
            +
             | 
| 421 | 
            +
              specify "events not persisted if append failed" do
         | 
| 422 | 
            +
                repository.append_to_stream([
         | 
| 423 | 
            +
                  TestDomainEvent.new(event_id: SecureRandom.uuid),
         | 
| 424 | 
            +
                ], 'stream', :none)
         | 
| 107 425 |  | 
| 108 | 
            -
                expect | 
| 109 | 
            -
             | 
| 110 | 
            -
             | 
| 111 | 
            -
             | 
| 426 | 
            +
                expect do
         | 
| 427 | 
            +
                  repository.append_to_stream([
         | 
| 428 | 
            +
                    TestDomainEvent.new(
         | 
| 429 | 
            +
                      event_id: '9bedf448-e4d0-41a3-a8cd-f94aec7aa763'
         | 
| 430 | 
            +
                    ),
         | 
| 431 | 
            +
                  ], 'stream', :none)
         | 
| 432 | 
            +
                end.to raise_error(RubyEventStore::WrongExpectedEventVersion)
         | 
| 433 | 
            +
                expect(repository.has_event?('9bedf448-e4d0-41a3-a8cd-f94aec7aa763')).to be_falsey
         | 
| 112 434 | 
             
              end
         | 
| 113 435 | 
             
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: ruby_event_store
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0. | 
| 4 | 
            +
              version: 0.19.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Arkency
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: exe
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2017- | 
| 11 | 
            +
            date: 2017-11-06 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: bundler
         |