require 'spec_helper'

describe FunWithJsonApi::SchemaValidators::CheckDocumentIdMatchesResource do
  describe '.call' do
    let(:schema_validator) do
      instance_double('FunWithJsonApi::SchemaValidator', resource_type: 'examples')
    end
    subject { described_class.call(schema_validator) }

    context 'when the resource is persisted' do
      let(:resource) { instance_double('ActiveRecord::Base', persisted?: true) }
      before { allow(schema_validator).to receive(:resource).and_return(resource) }

      context 'when /data/id does not match the resource id' do
        before do
          allow(schema_validator).to receive(:resource_id).and_return('11')
          allow(schema_validator).to receive(:document_id).and_return('42')
        end

        it 'raises a InvalidDocumentIdentifier error' do
          expect do
            subject
          end.to raise_error(FunWithJsonApi::Exceptions::InvalidDocumentIdentifier) do |e|
            expect(e.payload.size).to eq 1

            payload = e.payload.first
            expect(payload.code).to eq 'invalid_document_identifier'
            expect(payload.pointer).to eq '/data/id'
            expect(payload.title).to eq 'Request json_api data id is invalid'
            expect(payload.detail).to eq 'Expected data id to match resource at endpoint: 11'
            expect(payload.status).to eq '409'
          end
        end
      end

      context 'when /data/id is not a string' do
        before do
          allow(schema_validator).to receive(:document_id).and_return(42)
        end

        it 'raises a InvalidDocument error' do
          expect do
            subject
          end.to raise_error(FunWithJsonApi::Exceptions::InvalidDocumentIdentifier) do |e|
            expect(e.payload.size).to eq 1

            payload = e.payload.first
            expect(payload.code).to eq 'invalid_document_identifier'
            expect(payload.pointer).to eq '/data/id'
            expect(payload.title).to eq 'Request json_api data id is invalid'
            expect(payload.detail).to eq 'data id value must be a JSON String (i.e. "1234")'
            expect(payload.status).to eq '409'
          end
        end
      end
    end

    context 'when the resource is not persisted' do
      let(:resource) { instance_double('ActiveRecord::Base', persisted?: false) }
      before { allow(schema_validator).to receive(:resource).and_return(resource) }

      context 'when a document_id has been supplied' do
        before { allow(schema_validator).to receive(:document_id).and_return('42') }

        context 'when the deserializer does not have an id attribute' do
          let(:deserializer) { instance_double('FunWithJsonApi::Deserializer') }
          before do
            allow(schema_validator).to receive(:deserializer).and_return(deserializer)
            allow(deserializer).to receive(:attributes).and_return([])
          end

          it 'raises a IllegalClientGeneratedIdentifier error' do
            expect do
              subject
            end.to raise_error(FunWithJsonApi::Exceptions::IllegalClientGeneratedIdentifier) do |e|
              expect(e.payload.size).to eq 1

              payload = e.payload.first
              expect(payload.code).to eq 'illegal_client_generated_identifier'
              expect(payload.pointer).to eq '/data/id'
              expect(payload.title).to eq(
                'Request json_api attempted to set an unsupported client-generated id'
              )
              expect(payload.detail).to eq(
                "The current endpoint does not allow you to set an id for a new 'examples' resource"
              )
              expect(payload.status).to eq '403'
            end
          end
        end
        context 'when the deserializer has an id attribute' do
          let(:deserializer) { instance_double('FunWithJsonApi::Deserializer') }
          before do
            allow(schema_validator).to receive(:deserializer).and_return(deserializer)
            allow(deserializer).to receive(:attributes).and_return(
              [
                instance_double('FunWithJsonApi::Attribute', name: :id)
              ]
            )
          end

          context 'when /data/id is not a string' do
            before do
              allow(schema_validator).to receive(:document_id).and_return(42)
            end

            it 'raises a InvalidDocument error' do
              expect do
                subject
              end.to raise_error(FunWithJsonApi::Exceptions::InvalidDocumentIdentifier) do |e|
                expect(e.payload.size).to eq 1

                payload = e.payload.first
                expect(payload.code).to eq 'invalid_document_identifier'
                expect(payload.pointer).to eq '/data/id'
                expect(payload.title).to eq 'Request json_api data id is invalid'
                expect(payload.detail).to eq 'data id value must be a JSON String (i.e. "1234")'
                expect(payload.status).to eq '409'
              end
            end
          end

          context 'when a resource matching id exists' do
            before do
              allow(deserializer).to receive(:load_resource_from_id_value)
                .with('42')
                .and_return(double('existing_resource', id: '24'))
            end

            it 'raises a InvalidClientGeneratedIdentifier error' do
              expect do
                subject
              end.to raise_error(
                FunWithJsonApi::Exceptions::InvalidClientGeneratedIdentifier
              ) do |e|
                expect(e.payload.size).to eq 1

                payload = e.payload.first
                expect(payload.code).to eq 'invalid_client_generated_identifier'
                expect(payload.pointer).to eq '/data/id'
                expect(payload.title).to eq(
                  'Request json_api data id has already been used for an existing'\
                  ' resource'
                )
                expect(payload.detail).to eq(
                  "The provided id for a new 'examples' resource has already been used by another"\
                  ' resource: 42'
                )
                expect(payload.status).to eq '409'
              end
            end
          end
        end
      end
    end
  end
end
