require 'spec_helper'

describe Bogus::Shadow do
  let(:object) { Samples::FooFake.new }
  let(:shadow) { Bogus::Shadow.new }

  shared_examples_for "spying on shadows" do
    context "spying" do
      before do
        shadow.run(:foo, "a", "b") rescue nil # for when the method raises an error
      end

      it "returns the called methods" do
        expect(shadow.has_received(:foo, ["a", "b"])).to be(true)
      end

      it "does not return true for interactions that did not happen" do
        expect(shadow.has_received(:foo, ["a", "c"])).to be(false)
        expect(shadow.has_received(:bar, ["a", "c"])).to be(false)
      end
    end
  end

  context "unrecorded interactions" do
    it "returns an unstubbed value" do
      return_value = shadow.run(:foo, "a", "b")

      expect(return_value).to be_a_default_return_value
    end

    include_examples "spying on shadows"
  end

  context "interactions that raise exceptions" do
    class SomeWeirdException < StandardError; end

    before do
      shadow.stubs(:foo, "a", "b") { raise SomeWeirdException, "failed!" }
    end

    it "raises the error when called" do
      expect {
        shadow.run(:foo, "a", "b")
      }.to raise_error(SomeWeirdException, "failed!")
    end

    include_examples "spying on shadows"
  end

  context "interactions with no return value" do
    before do
      shadow.stubs(:foo, ["a", "b"])
    end

    it "returns the default value" do
      expect(shadow.run(:foo, ["a", "b"])).to be_a_default_return_value
    end

    include_examples "spying on shadows"
  end

  context "interactions with AnyArgs" do
    before do
      shadow.stubs(:foo, "a", "b") { "old specific value" }
      shadow.stubs(:foo, Bogus::AnyArgs) { "default value" }
      shadow.stubs(:foo, "a", "d") { "new specific value" }
    end

    it "changes the default value returned from method" do
      expect(shadow.run(:foo, "b", "c")).to eq("default value")
    end

    it "overwrites the old specific stubbed values" do
      expect(shadow.run(:foo, "a", "b")).to eq("default value")
    end

    it "does not affect the new specific stubbed values" do
      expect(shadow.run(:foo, "a", "d")).to eq("new specific value")
    end

    it "allows spying on calls using any args" do
      shadow.run(:foo, "a", "c")

      expect(shadow.has_received(:foo, [Bogus::AnyArgs])).to be(true)
    end
  end

  context "interactions that take anything" do
    before do
      shadow.stubs(:foo, "a", Bogus::Anything) { "return value" }
    end

    it "changes the return value for calls that match" do
      expect(shadow.run(:foo, "a", "c")).to eq("return value")
    end

    it "does not affect the return value for other calls" do
      shadow.stubs(:foo, "a", "b") { "specific value" }

      expect(shadow.run(:foo, "a", "b")).to eq("specific value")
    end

    it "allows spying on calls using anything in args" do
      shadow.run(:foo, "a", "b")

      expect(shadow.has_received(:foo, [Bogus::Anything, "b"])).to be(true)
    end
  end

  context "stubbed interactions" do
    before do
      shadow.stubs(:foo, "a", "b") { "stubbed value" }
    end

    it "returns the stubbed value" do
      expect(shadow.run(:foo, "a", "b")).to eq("stubbed value")
    end

    it "returns the latest stubbed value" do
      shadow.stubs(:foo, "a", "b") { "stubbed twice" }
      shadow.stubs(:foo, "b", "c") { "different params" }

      expect(shadow.run(:foo, "a", "b")).to eq("stubbed twice")
      expect(shadow.run(:foo, "b", "c")).to eq("different params")
    end

    it "returns the default value for non-stubbed calls" do
      expect(shadow.run(:foo, "c", "d")).to be_a_default_return_value
      expect(shadow.run(:bar)).to be_a_default_return_value
    end

    it "does not contribute towards unsatisfied interactions" do
      expect(shadow.unsatisfied_interactions).to be_empty
    end

    it "adds required interaction when mocking over stubbing" do
      shadow.mocks(:foo, "a", "b") { "stubbed value" }

      expect(shadow.unsatisfied_interactions).not_to be_empty
    end

    include_examples "spying on shadows"
  end

  context "mocked interactions" do
    before do
      shadow.mocks(:foo, "a", "b") { "mocked value" }
    end

    it "returns the mocked value" do
      expect(shadow.run(:foo, "a", "b")).to eq("mocked value")
    end

    it "overwrites the stubbed value" do
      shadow.stubs(:foo, "a", "c") { "stubbed value" }
      shadow.mocks(:foo, "a", "c") { "mocked value" }

      expect(shadow.run(:foo, "a", "c")).to eq("mocked value")
    end

    it "is overwritten by stubbing" do
      shadow.mocks(:foo, "a", "c") { "mocked value" }
      shadow.stubs(:foo, "a", "c") { "stubbed value" }

      expect(shadow.run(:foo, "a", "c")).to eq("stubbed value")
    end

    it "removes the required interaction when stubbing over mocking" do
      shadow.stubs(:foo, "a", "b") { "stubbed value" }

      expect(shadow.unsatisfied_interactions).to be_empty
    end

    it "returns the default value for non-stubbed calls" do
      expect(shadow.run(:foo, "a", "c")).to be_a_default_return_value
    end

    it "contributes towards unsatisfied interactions" do
      interactions = shadow.unsatisfied_interactions
      expect(interactions.size).to eq(1)
      expect(interactions.first.method).to eq(:foo)
      expect(interactions.first.args).to eq(["a", "b"])
    end

    it "removes the staisfied expectations from unsatisfied interactions" do
      shadow.mocks(:with_optional_args, 'a')
      shadow.run(:with_optional_args, 'a', Bogus::DefaultValue)
      shadow.run(:foo, "a", "b")

      expect(shadow.unsatisfied_interactions).to be_empty
    end

    include_examples "spying on shadows"
  end
end
