require 'spec_helper'

remote_describe "transactions testing" do
  require 'torquebox-messaging'
  require 'torquebox-cache'

  deploy <<-END.gsub(/^ {4}/,'')
    ---
    application:
      root: #{File.dirname(__FILE__)}/../apps/alacarte/transactions
    ruby:
      version: #{RUBY_VERSION[0,3]}
  END

  before(:each) do
    @input  = TorqueBox::Messaging::Queue.new('/queue/input')
    @no_xa_input = TorqueBox::Messaging::Queue.new('/queue/no_xa_input')
    @output = TorqueBox::Messaging::Queue.new('/queue/output')
    @cache  = TorqueBox::Infinispan::Cache.new(:name => 'tx-test')
    @default_dir = File.join(File.dirname(__FILE__), '..', 'Infinispan-FileCacheStore')
  end

  after(:each) do
    @cache.clear
  end

  after(:all) do
    @cache.stop
    FileUtils.rm_rf @default_dir
  end
    
  it "should not hang when receive times out" do
    response = nil
    thread = Thread.new {
      response = @output.receive(:timeout => 1)
    }
    @input.publish("anything")
    thread.join
    response.should be_nil
    @output.receive.should == 'yay!' # drain the queue for next test
  end

  it "should publish a message when no error occurs" do
    response = nil
    thread = Thread.new {
      response = @output.receive(:timeout => 10_000)
    }
    @input.publish("anything")
    thread.join
    response.should_not be_nil
  end

  it "should retry delivery when an error is tossed" do
    @input.publish("This message should trigger 5 retries")
    response = @output.receive(:timeout => 10_000)
    response.should match /success.*\s5\s/
  end

  it "should rollback published messages when an error is tossed" do
    response = nil
    thread = Thread.new {
      response = @output.receive(:timeout => 5_000)
    }
    @input.publish("This message should cause an error to be raised")
    thread.join
    response.should be_nil
  end

  it "should receive a message in the processor's transaction" do
    queue = TorqueBox::Messaging::Queue.start "/queue/foo"
    begin
      @input.publish("should receive from #{queue.name}")
      queue.publish("release")
      response = @output.receive(:timeout => 5_000)
      response.should == "got release"
    ensure
      queue.stop
    end
  end

  it "should rollback message when explicit transaction fails" do
    queue = TorqueBox::Messaging::Queue.start "/queue/foo"
    begin
      TorqueBox.transaction do
        queue.publish("whatevs")
        raise "rollback"
      end
      raise "should not get here"
    rescue Exception => e
      e.message.should == 'rollback'
      queue.receive(:timeout => 5_000).should be_nil
    ensure
      queue.stop
    end
  end

  it "should support nested messaging transactions" do
    queue = TorqueBox::Messaging::Queue.start "/queue/foo"
    begin
      TorqueBox.transaction do
        queue.publish("first")
        TorqueBox.transaction(:requires_new => false) do
          queue.publish("second")
        end
      end
      q = queue.to_a
      q[0].decode.should == 'first'
      q[1].decode.should == 'second'
      q.count.should == 2
    ensure
      queue.stop
    end
  end

  it "should support nested messaging transactions, the latter new" do
    queue = TorqueBox::Messaging::Queue.start "/queue/foo"
    begin
      TorqueBox.transaction do
        queue.publish("first")
        TorqueBox.transaction(:requires_new => true) do
          queue.publish("second")
        end
      end
      q = queue.to_a
      q[0].decode.should == 'second'
      q[1].decode.should == 'first'
      q.count.should == 2
    ensure
      queue.stop
    end
  end

  it "should rollback cache transactions" do
    begin
      TorqueBox.transaction do
        @cache.put("first", "1")
        raise "rollback"
      end
      raise "should not get here"
    rescue Exception => e
      e.message.should == 'rollback'
      @cache.keys.size.should == 0
    end
  end

  it "should not rollback cache transactions when suspended" do
    begin
      TorqueBox.transaction do
        @cache.put("first", "1")
        TorqueBox.transaction(:none) do
          @cache.put("second", 2)
          raise "rollback"
        end
      end
      raise "should not get here"
    rescue Exception => e
      e.message.should == 'rollback'
      @cache.keys.size.should == 1
      @cache.get("second").should == 2
    end
  end

  it "should rollback nested cache transactions" do
    begin
      TorqueBox.transaction do
        @cache.put("first", "1")
        TorqueBox.transaction(:requires_new => false) do
          @cache.put("second", "2")
          raise "rollback"
        end
      end
      raise "should not get here"
    rescue Exception => e
      e.message.should == 'rollback'
      @cache.keys.size.should == 0
    end
  end

  it "should rollback nested messaging transactions" do
    queue = TorqueBox::Messaging::Queue.start "/queue/foo"
    begin
      TorqueBox.transaction do
        queue.publish("first")
        TorqueBox.transaction(:requires_new => false) do
          queue.publish("second")
          raise "rollback"
        end
      end
      raise "should not get here"
    rescue Exception => e
      e.message.should == 'rollback'
      queue.count.should == 0
    ensure
      queue.stop
    end
  end

  it "should rollback nested messaging transactions, the latter new" do
    queue = TorqueBox::Messaging::Queue.start "/queue/foo"
    begin
      TorqueBox.transaction do
        queue.publish("first")
        begin
          TorqueBox.transaction(:requires_new => true) do
            queue.publish("second")
            raise "rollback"
          end
        rescue Exception => e
          e.message.should == 'rollback'
        end
      end
      queue.first.decode.should == 'first'
      queue.count.should == 1
    ensure
      queue.stop
    end
  end

  it "should not rollback non-transactional messages" do
    queue = TorqueBox::Messaging::Queue.start "/queue/foo"
    begin
      TorqueBox.transaction do
        queue.publish("first")
        queue.publish("second", :tx => false)
        raise "rollback"
      end
      raise "should not get here"
    rescue Exception => e
      e.message.should == 'rollback'
      queue.first.decode.should == 'second'
      queue.count.should == 1
    ensure
      queue.stop
    end
  end

  context "without xa" do
    it "should retry delivery when an error is tossed" do
      @no_xa_input.publish("This message should trigger 5 retries")
      response = @output.receive(:timeout => 10_000)
      puts "!!! RESPONSE IS #{response}"
      response.should match /success.*\s5\s/
    end

    it "should not rollback published messages when an error is tossed" do
      responses = []
      thread = Thread.new {
        # default messages retry 10 times
        10.times {
          responses << @output.receive(:timeout => 5_000)
        }
      }
      @no_xa_input.publish("This message should cause an error to be raised")
      thread.join
      responses.count.should == 10
      responses.each { |response| response.should == "yay!" }
    end
  end

end
