require "mock5"

describe Mock5 do
  describe ".mock" do
    it "creates an Api" do
      expect(described_class::Api).to receive(:new).with(/foo/).and_yield
      described_class.mock /foo/ do
        # mock definition goes here
      end
    end

    it "returns an Api" do
      expect(described_class.mock).to be_kind_of(described_class::Api)
    end
  end

  describe "API mgmt" do
    before do
      WebMock::StubRegistry.instance.reset!
      described_class.instance_exec do
        if instance_variable_defined?(:@_mounted_apis)
          remove_instance_variable :@_mounted_apis
        end
      end
    end

    let(:mounted_apis){ described_class.mounted_apis }
    let(:mounted_apis_qty){ mounted_apis.size }
    let(:api){ described_class.mock }
    let(:another_api){ described_class.mock }

    describe ".mount" do
      it "raises ArgumentError when passed an invalid argument" do
        action = ->{ described_class.mount nil }
        message = "expected an instance of Mock5::Api"
        expect(&action).to raise_error(ArgumentError, message)
      end
      it "mounts an api" do
        described_class.mount api
        expect(mounted_apis).to include(api)
      end

      it "mounts an api only once" do
        10.times{ described_class.mount api }
        expect(mounted_apis_qty).to eq(1)
      end

      it "mounts several APIs at once" do
        described_class.mount api, another_api
        expect(mounted_apis).to include(api)
        expect(mounted_apis).to include(another_api)
      end

      it "returns the list of mounted apis" do
        expect(described_class.mount(api)).to eq([api].to_set)
        expect(described_class.mount(api, another_api)).to eq([another_api].to_set)
      end
    end

    describe ".unmount" do
      before{ described_class.mount api }

      it "unmounts mounted api" do
        described_class.unmount api
        expect(mounted_apis).to be_empty
      end

      it "unmounts api only once" do
        10.times{ described_class.unmount api }
        expect(mounted_apis).to be_empty
      end

      it "unmounts several APIs at once" do
        described_class.mount another_api
        expect(mounted_apis_qty).to eq(2)
        described_class.unmount api, another_api
        expect(mounted_apis).to be_empty
      end

      it "only unmount specified api" do
        described_class.mount another_api
        described_class.unmount api
        expect(mounted_apis).to include(another_api)
      end

      it "returns the list of unmounted apis" do
        expect(described_class.unmount(another_api)).to be_empty
        expect(described_class.unmount(api, another_api)).to eq([api].to_set)
      end
    end

    describe ".unmount_all!" do
      before do
        3.times{ described_class.mount described_class.mock }
      end

      it "unmounts all currently mounted apis" do
        expect(mounted_apis_qty).to eq(3)
        described_class.unmount_all!
        expect(mounted_apis).to be_empty
      end

      it "has .reset! alias" do
        expect(mounted_apis_qty).to eq(3)
        described_class.reset!
        expect(mounted_apis).to be_empty
      end
    end

    describe ".mounted?" do
      before{ described_class.mount api }

      it "returns true if api is currently mounted" do
        expect(described_class.mounted?(api)).to be_truthy
      end

      it "returns false if api is currently not mounted" do
        expect(described_class.mounted?(another_api)).to be_falsy
      end

      it "returns true only when ALL api are mounted" do
        action = ->{ described_class.mount another_api }
        result = ->{ described_class.mounted? api, another_api }
        expect(&action).to change(&result).from(false).to(true)
      end
    end

    describe ".with_mounted" do
      it "temporary mounts an API" do
        action = -> do
          described_class.with_mounted api do
            expect(mounted_apis).to include(api)
          end
        end

        expect(mounted_apis).to be_empty
        expect(&action).not_to change(mounted_apis, :empty?)
      end

      it "doesn't unmount api, that was mounted before" do
        described_class.mount api

        described_class.with_mounted api, another_api do
          expect(mounted_apis).to include(another_api)
        end

        expect(mounted_apis).to include(api)
        expect(mounted_apis).not_to include(another_api)
      end
    end

    describe "stubbing" do
      def get(url)
        Net::HTTP.get(URI(url))
      end

      def post(url, params={})
        Net::HTTP.post_form(URI(url), params).body
      end

      let(:api) do
        described_class.mock "http://example.com" do
          get "/index.html" do
            "index.html"
          end

          post "/submit/here" do
            "submit"
          end
        end
      end

      let(:another_api) do
        described_class.mock "http://example.com" do
          post "/foo/:foo" do
            params["foo"]
          end

          get "/bar/:bar" do
            params["bar"]
          end
        end
      end

      before{ described_class.mount api, another_api }

      it "stubs remote apis" do
        expect(get("http://example.com/index.html?foo=bar")).to eq("index.html")
        expect(post("http://example.com/submit/here?foo=bar")).to eq("submit")
        expect(post("http://example.com/foo/bar?fizz=buzz")).to eq("bar")
        expect(get("http://example.com/bar/foo")).to eq("foo")
      end
    end
  end
end