require 'spec_helper'

GUID_REGEXP = /^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$/i

module InvocationTestHelper
  def new_invocation(payload=@payload)
    Theatre::Invocation.new(@namespace, @block, @payload)
  end
end

describe "The lifecycle of an Invocation" do

  include InvocationTestHelper

  before :all do
    @block   = lambda {}
    @payload = 123
    @namespace = "/some/namespace"
  end

  it "should have an initial state of :new" do
    new_invocation.current_state.should eql(:new)
  end

  it "should not have a @queued_time until state becomes :queued" do
    invocation = new_invocation
    invocation.queued_time.should eql(nil)
    invocation.queued
    invocation.queued_time.should be_instance_of(Time)
    invocation.current_state.should eql(:queued)
  end

  it "should have a valid guid when instantiated" do
    new_invocation.unique_id.should =~ GUID_REGEXP
  end

  it "should execute the callback when moving to the 'start' state" do
    flexmock(@block).should_receive(:call).once
    invocation = new_invocation
    invocation.queued
    invocation.start
  end

end

describe "Using Invocations that've been ran through the Theatre" do

  it "should pass the payload to the callback" do
    destined_payload = [:i_feel_so_pretty, :OH, :SO, :PRETTY!]
    expecting_callback = lambda do |payload|
      payload.should equal(destined_payload)
    end
    invocation = Theatre::Invocation.new("/namespace/whatever", expecting_callback, destined_payload)
    invocation.queued
    invocation.start
  end

  it "should have a status of :error if an exception was raised and set the #error property" do
    invocation = Theatre::Invocation.new("/namespace/whatever", lambda { raise ArgumentError, "this error is intentional" })
    invocation.queued
    invocation.start
    invocation.current_state.should == :error
    invocation.should be_error
    invocation.error.should be_instance_of(ArgumentError)
  end

  it "should trigger an exception event if an exception was raised" do
    invocation = Theatre::Invocation.new("/namespace/whatever", lambda { raise ArgumentError, "this error is intentional" })
    flexmock(Adhearsion::Events).should_receive(:trigger).once.with('/exception', FlexMock.any)
    invocation.queued
    invocation.start
  end

  it "should NOT trigger an exception if the exception occurs in an exception namespace" do
    invocation = Theatre::Invocation.new("/exception", lambda { raise ArgumentError, "this error is intentional" })
    flexmock(Adhearsion::Events).should_receive(:trigger).never
    invocation.queued
    invocation.start
  end

  it "should do the right thing with exceptions triggered with the wrong namespace" do
    invocation = Theatre::Invocation.new(['exception'], lambda { raise ArgumentError, "this error is intentional" })
    flexmock(Adhearsion::Events).should_receive(:trigger).never
    invocation.queued
    invocation.start
  end

  it "should have a status of :success if no expection was raised" do
    callback = lambda { "No errors raised here!" }
    invocation = Theatre::Invocation.new("/namespace/whatever", callback)
    invocation.queued
    invocation.start
    invocation.current_state.should equal(:success)
    invocation.should be_success
  end

  it "should set the #returned_value property to the returned value callback when a payload was given" do
    doubler = lambda { |num| num * 2 }
    invocation = Theatre::Invocation.new('/foo/bar', doubler, 5)
    invocation.queued
    invocation.start
    invocation.returned_value.should equal(10)
  end

  it "should set the #returned_value property to the returned value callback when a payload was NOT given" do
    doubler = lambda { :ohai }
    invocation = Theatre::Invocation.new('/foo/bar', doubler)
    invocation.queued
    invocation.start
    invocation.returned_value.should equal(:ohai)
  end

  it "should set the #finished_time property when a success was encountered" do
    block = lambda {}
    invocation = Theatre::Invocation.new('/foo/bar', block)
    invocation.queued

    now = Time.now
    flexmock(Time).should_receive(:now).twice.and_return now

    invocation.start
    invocation.should be_success
  end

  it "should set the #finished_time property when a failure was encountered" do
    invocation = Theatre::Invocation.new('/foo/bar', lambda { raise LocalJumpError })
    invocation.queued

    invocation.start
    invocation.should be_error
    invocation.finished_time.should be_kind_of(Time)
  end

  it "should set the #started_time property after starting" do
    invocation = Theatre::Invocation.new('/foo/bar', lambda { sleep 0.01 } )
    invocation.queued
    invocation.started_time.should be_nil
    invocation.start
    invocation.started_time.should be_kind_of(Time)
  end

  it "should properly calculate #execution_duration" do
    time_ago_difference = 60 * 5 # Five minutes
    time_now = Time.now
    time_ago = time_now - time_ago_difference

    invocation = Theatre::Invocation.new('/foo/bar', lambda {} )
    invocation.queued
    invocation.start

    invocation.send(:instance_variable_set, :@started_time, time_ago)
    invocation.send(:instance_variable_set, :@finished_time, time_now)

    invocation.execution_duration.should be_within(0.01).of(time_ago_difference.to_f)
  end

  it "should return the set value of returned_value when one has been set to a non-nil value" do
    return_nil = lambda { 123 }
    invocation = Theatre::Invocation.new("/namespace/whatever", return_nil)
    invocation.queued
    invocation.start
    invocation.returned_value.should eql(123)
  end

  it "should return nil for returned_value when it has been set to nil" do
    return_nil = lambda { nil }
    invocation = Theatre::Invocation.new("/namespace/whatever", return_nil)
    invocation.queued
    invocation.start
    invocation.returned_value.should eql(nil)
  end

  it "waiting on an Invocation should execute properly" do
    wait_on_invocation = lambda { 123 }
    invocation = Theatre::Invocation.new("/namespace/whatever", wait_on_invocation)
    invocation.queued
    invocation.start
    invocation.wait.should eql(123)
    invocation.success?.should eql(true)
  end

end