require 'spec_helper'
require 'pact/request'

shared_examples "a request" do

  describe 'matching' do
    let(:expected) do
      Pact::Request::Expected.from_hash(
        {'method' => 'get', 'path' => 'path', 'query' => /b/}
        )
    end

    let(:actual) do
      Pact::Request::Actual.from_hash({'method' => 'get', 'path' => 'path', 'query' => 'blah'})
    end

    it "should match" do
      expect(expected.difference(actual)).to eq({})
    end
  end

  describe 'full_path' do
    context "with empty path" do
      subject { described_class.from_hash({:path => '', :method => 'get'}) }
      it "returns the full path"do
        expect(subject.full_path).to eq "/"
      end
    end
    context "with a path" do
      subject { described_class.from_hash({:path => '/path', :method => 'get'}) }
      it "returns the full path"do
        expect(subject.full_path).to eq "/path"
      end
    end
    context "with a path and query" do
      subject { described_class.from_hash({:path => '/path', :method => 'get', :query => "something"}) }
      it "returns the full path"do
        expect(subject.full_path).to eq "/path?something"
      end
    end
  end

  describe "building from a hash" do

    let(:raw_request) do
      {
        'method' => 'get',
        'path' => '/mallory',
        'headers' => {
          'Content-Type' => 'application/json'
        },
        'body' => 'hello mallory'
      }
    end

    subject { described_class.from_hash(raw_request) }

    its(:method) { should == 'get' }
    its(:path) { should == '/mallory' }
    its(:body) { should == 'hello mallory' }
    its(:query) { should eq Pact::Request::NullExpectation.new }

    it "blows up if method is absent" do
      raw_request.delete 'method'
      expect { described_class.from_hash(raw_request) }.to raise_error
    end

    it "blows up if path is absent" do
      raw_request.delete 'path'
      expect { described_class.from_hash(raw_request) }.to raise_error
    end

    it "does not blow up if body is missing" do
      raw_request.delete 'body'
      expect { described_class.from_hash(raw_request) }.to_not raise_error
    end

  end
end

module Pact

  describe Request::Expected do
    it_behaves_like "a request"

    describe "as_json_with_options" do
      subject { Request::Expected.new(:get, '/path', {:header => 'value'}, {:body => 'yeah'}, "query", options) }
      context "with options" do
        let(:options) { {some: 'options'} }
        it "includes the options" do
          expect(subject.as_json_with_options[:options]).to eq options
        end
      end
      context "without options" do
        let(:options) { {} }
        it "does not include the options" do
          expect(subject.as_json_with_options.key?(:options)).to be_false
        end
      end
    end
    describe "as_json" do
      subject { Request::Expected.new(:get, '/path', {:header => 'value'}, {:body => 'yeah'}, "query", {some: 'options'}) }
      context "with options" do
        it "does not include the options" do
          expect(subject.as_json.key?(:options)).to be_false
        end
      end
    end

    describe "matching to actual requests" do

      subject { Request::Expected.new(expected_method, expected_path, expected_headers, expected_body, expected_query, options) }
      let(:options) { {} }

      let(:expected_method) { 'get' }
      let(:expected_path) { '/foo' }
      let(:expected_headers) { nil }
      let(:expected_body) { nil }
      let(:expected_query) { '' }

      let(:actual_request) { Request::Actual.new(actual_method, actual_path, actual_headers, actual_body, actual_query) }

      let(:actual_method) { 'get' }
      let(:actual_path) { '/foo' }
      let(:actual_headers) { nil }
      let(:actual_body) { nil }
      let(:actual_query) { '' }

      it "matches identical requests" do
        expect(subject.match actual_request).to be_true
      end

      context "when the methods are the same but one is symbolized" do
        let(:expected_method) { :get }
        let(:actual_method) { 'get' }

        it "matches" do
          expect(subject.match actual_request).to be_true
        end
      end

      context "when the methods are different" do
        let(:expected_method) { 'get' }
        let(:actual_method) { 'post' }

        it "does not match" do
          expect(subject.match actual_request).to be_false
        end
      end

      context "when the paths are different" do
        let(:expected_path) { '/foo' }
        let(:actual_path) { '/bar' }

        it "does not match" do
          expect(subject.match actual_request).to be_false
        end
      end

      context "when the paths vary only by a trailing slash" do
        let(:expected_path) { '/foo' }
        let(:actual_path) { '/foo/' }

        it "matches" do
          expect(subject.match actual_request).to be_true
        end
      end

      context "when the expected body is nil and the actual body is empty" do
        let(:expected_body) { nil }
        let(:actual_body) { '' }

        it "does not match" do
          expect(subject.match actual_request).to be_false
        end
      end

      context "when the expected body has no expectation and the actual body is empty" do
        let(:expected_body) { Request::NullExpectation.new }
        let(:actual_body) { '' }

        it "matches" do
          expect(subject.match actual_request).to be_true
        end
      end

      context "when the expected body is nested and the actual body is nil" do
        let(:expected_body) do
          {
            a: 'a'
          }
        end

        let(:actual_body) { nil }

        it "does not match" do
          expect(subject.match actual_request).to be_false
        end
      end

      context "when the bodies are different" do
        let(:expected_body) { 'foo' }
        let(:actual_body) { 'bar' }

        it "does not match" do
          expect(subject.match actual_request).to be_false
        end
      end

      context "when the expected body contains matching regexes" do
        let(:expected_body) do
          {
            name: 'Bob',
            customer_id: /CN.*/
          }
        end

        let(:actual_body) do
          {
            name: 'Bob',
            customer_id: 'CN1234'
          }
        end

        it "matches" do
          expect(subject.match actual_request).to be_true
        end
      end

      context "when the expected body contains non-matching regexes" do
        let(:expected_body) do
          {
            name: 'Bob',
            customer_id: /foo/
          }
        end

        let(:actual_body) do
          {
            name: 'Bob',
            customer_id: 'CN1234'
          }
        end

        it "does not match" do
          expect(subject.match actual_request).to be_false
        end
      end

      context "when the expected body contains matching terms" do
        let(:expected_body) do
          {
            name: 'Bob',
            customer_id: Term.new(matcher: /CN.*/, generate: 'CN789')
          }
        end

        let(:actual_body) do
          {
            name: 'Bob',
            customer_id: 'CN1234'
          }
        end

        it "matches" do
          expect(subject.match actual_request).to be_true
        end
      end

      context "when the expected body contains non-matching terms" do
        let(:expected_body) do
          {
            name: 'Bob',
            customer_id: Term.new(matcher: /foo/, generate: 'fooool')
          }
        end

        let(:actual_body) do
          {
            name: 'Bob',
            customer_id: 'CN1234'
          }
        end

        it "does not match" do
          expect(subject.match actual_request).to be_false
        end
      end

      context "when the expected body contains non-matching arrays" do
        let(:expected_body) do
          {
            name: 'Robert',
            nicknames: ['Bob', 'Bobert']
          }
        end

        let(:actual_body) do
          {
            name: 'Bob',
            nicknames: ['Bob']
          }
        end

        it "does not match" do
          expect(subject.match actual_request).to be_false
        end
      end
      context "when the expected body contains non-matching hash where one field contains a substring of the other" do
          let(:expected_body) do
            {
              name: 'Robert',
            }
          end

          let(:actual_body) do
            {
              name: 'Rob'
            }
          end

          it "does not match" do
            expect(subject.match actual_request).to be_false
          end
      end

      context "when the expected body contains matching arrays" do
        let(:expected_body) do
          {
            name: 'Robert',
            nicknames: ['Bob', 'Bobert']
          }
        end

        let(:actual_body) do
          {
            name: 'Robert',
            nicknames: ['Bob', 'Bobert']
          }
        end

        it "does not match" do
          expect(subject.match actual_request).to be_true
        end
      end

      context "when the queries are different" do
        let(:expected_query) { 'foo' }
        let(:actual_query) { 'bar' }

        it "does not match" do
          expect(subject.match actual_request).to be_false
        end
      end

      context 'when there is no query expectation' do
        let(:expected_query) { Request::NullExpectation.new }
        let(:actual_query) { 'bar' }

        it 'matches' do
          expect(subject.match actual_request).to be_true
        end
      end

      context "when a string is expected, but a number is found" do
        let(:actual_body) { { thing: 123} }
        let(:expected_body) { { thing: "123" } }

        it 'does not match' do
          expect(subject.match actual_request).to be_false
        end
      end

      context "when unexpected keys are found in the body" do
        let(:expected_body) { {a: 1} }
        let(:actual_body) { {a: 1, b: 2} }
        context "when allowing unexpected keys" do
          let(:options) { {'allow_unexpected_keys_in_body' => true} } #From json, these will be strings
          it "matches" do
            expect(subject.match actual_request).to be_true
          end
        end
        context "when not allowing unexpected keys" do
          let(:options) { {'allow_unexpected_keys_in_body' => false} }
          it "does not match" do
            expect(subject.match actual_request).to be_false
          end
        end
      end
    end
  end

  describe Request::Actual do
    it_behaves_like "a request"
  end
end