require 'spec_helper'

RSpec.describe JSON::SchemaBuilder::Schema, type: :unit do
  subject(:schema) do
    Class.new do
      include JSON::SchemaBuilder

      def example
        object do
          integer :a

          object :b do
            integer :c
          end

          array :array do
            items do
              object do
                integer :foo

                object :bar do
                  integer :baz
                end
              end
            end
          end

          entity :anything do
            any_of [
              null,
              string,
              object {
                string :one
                string :two
              }
            ]
          end
        end
      end
    end.new.example.schema
  end

  let(:other) do
    Class.new do
      include JSON::SchemaBuilder

      def example
        object do
          integer :a2

          object :b do
            integer :d
          end

          array :array do
            items do
              object do
                object :bar do
                  integer :qux
                end
              end
            end
          end

          entity :anything do
            any_of [
              null,
              object {
                string :two
                string :three
              }
            ]
          end
        end
      end
    end.new.example.schema
  end

  let(:merged) do
    {
      "type" => :object,
      "properties" => {
        "a" => {
          "type" => "integer"
        },
        "a2" => {
          "type" => "integer"
        },
        "b" => {
          "type" => "object",
          "properties" => {
            "c" => {
              "type" => "integer"
            },
            "d" => {
              "type" => "integer"
            }
          }
        },
        "array" => {
          "type" => "array",
          "items" => {
            "type" => "object",
            "properties" => {
              "foo" => {
                "type" => "integer",
              },
              "bar" => {
                "type" => "object",
                "properties" => {
                  "baz" => {
                    "type" => "integer"
                  },
                  "qux" => {
                    "type" => "integer"
                  }
                }
              }
            }
          }
        },
        "anything" => {
          "anyOf" => [
            {
              "type" => "null"
            },
            {
              "type" => "string"
            },
            {
              "type" => "object",
              "properties" => {
                "one" => {
                  "type" => "string"
                },
                "two" => {
                  "type" => "string"
                },
                "three" => {
                  "type" => "string"
                }
              }
            }
          ]
        }
      }
    }
  end

  its(:data){ is_expected.to be_a(HashWithIndifferentAccess) }

  describe '#merge' do
    it 'should deep merge' do
      merged_schema = schema.merge other
      expect(merged_schema).to be_a described_class
      expect(merged_schema.data).to eql merged
    end

    it 'should not modify the source schema' do
      expect{ schema.merge other }.to_not change{ schema.data }
    end

    it 'should not modify the merging schema' do
      expect{ schema.merge other }.to_not change{ other.data }
    end
  end

  describe '#merge!' do
    it 'should deep merge in place' do
      merged_schema = schema.merge! other
      expect(merged_schema).to be_a described_class
      expect(merged_schema.data).to eql merged
    end

    it 'should not modify the merging schema' do
      expect{ schema.merge! other }.to_not change { other.data }
    end
  end

  %w(validate validate! fully_validate).each do |validator|
    describe "##{ validator }" do
      it "should #{ validator }" do
        expect(JSON::Validator).to receive(validator)
          .with schema.as_json, { }, opts: true
        schema.send validator, { }, opts: true
      end
    end
  end

  describe "#fragments" do
    context "with an unmerged schema" do
      subject(:fragments) { schema.fragments }
      its(:keys) do
        is_expected.to match_array %w(
          #/
          #/a
          #/b
          #/b/c
          #/array
          #/array/foo
          #/array/bar
          #/array/bar/baz
          #/anything
          #/anything/one
          #/anything/two
        )
      end

      it "stores the schema by fragment" do
        expect(fragments["#/"].first.as_json).to eq schema.as_json
      end

      it "structures the fragments correctly" do
        expect(fragments.values.flatten).to all be_a JSON::SchemaBuilder::Entity
        expect(fragments["#/"].length).to eq 1
        expect(fragments["#/"].length).to eq 1
        expect(fragments["#/a"].length).to eq 1
        expect(fragments["#/b"].length).to eq 1
        expect(fragments["#/b/c"].length).to eq 1
        expect(fragments["#/array"].length).to eq 3
        expect(fragments["#/array/foo"].length).to eq 1
        expect(fragments["#/array/bar"].length).to eq 1
        expect(fragments["#/array/bar/baz"].length).to eq 1
        expect(fragments["#/anything"].length).to eq 4
        expect(fragments["#/anything/one"].length).to eq 1
        expect(fragments["#/anything/two"].length).to eq 1
      end
    end

    context "with a merged schema" do
      subject(:fragments) { schema.merge(other).fragments }
      its(:keys) do
        is_expected.to match_array %w(
          #/
          #/a
          #/a2
          #/b
          #/b/c
          #/b/d
          #/array
          #/array/foo
          #/array/bar
          #/array/bar/baz
          #/array/bar/qux
          #/anything
          #/anything/one
          #/anything/two
          #/anything/three
        )
      end

      it "stores the schema by fragment" do
        expect(fragments["#/"].first.as_json).to eq schema.as_json
      end

      it "structures the fragments correctly" do
        expect(fragments.values.flatten).to all be_a JSON::SchemaBuilder::Entity
        expect(fragments["#/"].length).to eq 2
        expect(fragments["#/a"].length).to eq 1
        expect(fragments["#/a2"].length).to eq 1
        expect(fragments["#/b"].length).to eq 2
        expect(fragments["#/b/c"].length).to eq 1
        expect(fragments["#/b/d"].length).to eq 1
        expect(fragments["#/array"].length).to eq 6
        expect(fragments["#/array/foo"].length).to eq 1
        expect(fragments["#/array/bar"].length).to eq 2
        expect(fragments["#/array/bar/baz"].length).to eq 1
        expect(fragments["#/array/bar/qux"].length).to eq 1
        expect(fragments["#/anything"].length).to eq 7
        expect(fragments["#/anything/one"].length).to eq 1
        expect(fragments["#/anything/two"].length).to eq 2
        expect(fragments["#/anything/three"].length).to eq 1
      end
    end
  end
end