<commit_before>module Intermodal
  module RSpec
    module Resources
      extend ActiveSupport::Concern

      included do
        include Intermodal::RSpec::Rack
        include Intermodal::RSpec::AuthenticatedRequests
        include Intermodal::RSpec::PaginatedCollection

        let(:api) { raise "Must define let(:api)" }

        let(:resource_collection_name) { resource_name.pluralize }
        let(:model) { resource_name.camelize.constantize }
        let(:parent_models) { parent_names.map { |p| p.to_s.singularize.camelize.constantize } }

        let(:model_collection) do
          3.times { model.make!(model_factory_options) }
          (model_parent ? model.by_parent(model_parent) : model.all )
        end

        let(:model_resource) { model.make!(model_factory_options) }
        let(:model_factory_options) { (model_parent ? { parent_names.last.to_s.singularize => model_parent } : {} ) }
        let(:model_parents) { parent_models.map { |p| p.make! } }
        let(:model_parent) { model_parents.last }
        let(:presenter) { api.presenter_for model }

        let(:collection) { model_collection }
        let(:paginated_collection) { model_collection.paginate(:page => page, :per_page => per_page) }
        let(:presented_collection) { parser.decode(paginated_collection.send("to_#{format}", :presenter => presenter, :root => collection_element_name, :scope => presenter_scope_for_index)) }
        let(:page) { 1 }
        let(:per_page) { api.default_per_page }

        let(:resource_element_name) { model.name.demodulize.underscore }
        let(:collection_element_name) { resource_element_name.pluralize }
        let(:expected_resource) { model.find(model_resource.id) }
        let(:persisted_resource_id) { body['id'] }
        let(:resource_after_create) { model.find(persisted_resource_id) }
        let(:resource_after_update) { model.find(resource_id) }
        let(:resource_after_destroy) { resource_after_update }
        let(:resource) { parser.decode(expected_resource.send("to_#{format}", :presenter => presenter, :scope => presenter_scope)) }
        let(:presenter_scope) { nil }
        let(:presenter_scope_for_index) { presenter_scope }
        let(:resource_id) { resource['id'] }
        let(:parent_ids) { parent_names.zip(model_parents.map { |m| m.id }) }

        let(:namespace) { nil }
        let(:namespace_path) { ( namespace ? "/#{namespace}" : nil ) }
        let(:parent_path) { parent_ids.map { |p, p_id| "/#{p}/#{p_id}" }.join }
        let(:collection_url) { [ namespace_path, parent_path, "/#{resource_collection_name}.#{format}" ].join }
        let(:resource_url) { [ namespace_path, parent_path, "/#{resource_collection_name}/#{resource_id}.#{format}" ].join }

        let(:request_json_payload) { request_payload.to_json }
        let(:request_xml_payload) { request_payload.to_xml(:root => resource_element_name) }

        let(:malformed_json_payload) { '{ "bad": [ "data": ] }' }
        let(:malformed_xml_payload) { '<bad><data></bad></data>' }

        # DELETE specs expect record to be deleted.
        # Override this if you are using something such as ActiveResource:
        # let(:record_not_found_error) { ActiveResource::ResourceNotFound }
        let(:record_not_found_error) { ActiveRecord::RecordNotFound }

        # Some of the test examples assume that the database is blank.
        # For example, index tests require injecting 3 resources and expects
        # the index endpoint to return exactly 3 resources.
        let(:reset_datastore!) { } # Do nothing by default
      end

      module ClassMethods
        def given_create_attributes(values = {})
          let(:valid_create_attributes) { values }
        end

        def given_update_attributes(values = {})
          let(:valid_update_attributes) { values }
        end

        def metadata_for_resources(resource_name, options = {})
          if resource_name.is_a?(Array)
            parents = resource_name
            resource_name = parents.pop
          end
          resource_name = resource_name.to_s if resource_name.is_a?(Symbol)

          { :formats => [ :json ],
            :resource_name => resource_name,
            :model_name => resource_name.singularize,
            :encoding => 'utf-8',
            :parents => parents || [],
            :namespace_path => ( options[:namespace] ? "/#{options[:namespace]}" : nil )
          }.merge(options)
        end

        def metadata_for_formatted_resource(format, options = {})
          { :format => format,
            :collection_url => collection_url_for_resource(options[:namespace_path], options[:parents], options[:resource_name], format),
            :resource_url => resource_url_for_resource(options[:namespace_path], options[:parents], options[:resource_name], format),
            :mime_type => Mime::Type.lookup_by_extension(format).to_s
          }.merge(options)
        end

        def parent_path_for_resource(parents)
          parents.map { |p| "/#{p}/:id" }.join
        end

        def collection_url_for_resource(namespace, parents, resource_name, format)
          [ namespace, parent_path_for_resource(parents), "/#{resource_name}.#{format}" ].join
        end

        def resource_url_for_resource(namespace, parents, resource_name, format)
          [ namespace, parent_path_for_resource(parents), "/#{resource_name}/:id.#{format}" ].join
        end

        def resources(resource_name, options = {}, &blk)
          options = metadata_for_resources(resource_name, options)
          _resource_name = options[:resource_name].singularize

          # If you want xml, pass it as formats: [ :json, :xml ]
          options[:formats].each do |format|
            format_options = metadata_for_formatted_resource(format, options)
            context format_options[:collection_url], format_options do
              let(:namespace) { options[:namespace] }
              let(:resource_name) { _resource_name }
              let(:parent_names) { options[:parents] }
              let(:format) { format }
              let(options[:parents].last) { model_parent } if options[:parents].last
              instance_eval(&blk) if blk
            end
          end
        end

        def expects_resource_crud(options = {}, &blk)
          expected_actions = options[:only] || [ :index, :show, :create, :update, :destroy ]
          expected_actions -= options[:except] if options[:except]

          expected_actions.each do |action|
            send("expects_#{action}")
          end
        end

        STANDARD_SUCCESSFUL_STATUS_FOR = {
          :index => 200,
          :show => 200,
          :create => 201,
          :update => 200,
          :destroy => 204 }

        STANDARD_REQUEST_FOR = {
          :index => { :method => :get, :end_point => :collection_url },
          :show => { :method => :get, :end_point => :resource_url },
          :create => { :method => :post, :end_point => :collection_url, :payload => proc do valid_create_attributes end },
          :update => { :method => :put, :end_point => :resource_url, :payload => proc do valid_update_attributes end },
          :destroy => { :method => :delete, :end_point => :resource_url } }

        def request_resource_action(action, options = {}, &blk)
          options = {
            :mime_type => metadata[:mime_type],
            :encoding => metadata[:encoding],
            :status => STANDARD_SUCCESSFUL_STATUS_FOR[action],
            :collection_url => metadata[:collection_url],<commit_msg>[RSPEC] expects_resource_crud should eval additional examples<commit_after>module Intermodal
  module RSpec
    module Resources
      extend ActiveSupport::Concern

      included do
        include Intermodal::RSpec::Rack
        include Intermodal::RSpec::AuthenticatedRequests
        include Intermodal::RSpec::PaginatedCollection

        let(:api) { raise "Must define let(:api)" }

        let(:resource_collection_name) { resource_name.pluralize }
        let(:model) { resource_name.camelize.constantize }
        let(:parent_models) { parent_names.map { |p| p.to_s.singularize.camelize.constantize } }

        let(:model_collection) do
          3.times { model.make!(model_factory_options) }
          (model_parent ? model.by_parent(model_parent) : model.all )
        end

        let(:model_resource) { model.make!(model_factory_options) }
        let(:model_factory_options) { (model_parent ? { parent_names.last.to_s.singularize => model_parent } : {} ) }
        let(:model_parents) { parent_models.map { |p| p.make! } }
        let(:model_parent) { model_parents.last }
        let(:presenter) { api.presenter_for model }

        let(:collection) { model_collection }
        let(:paginated_collection) { model_collection.paginate(:page => page, :per_page => per_page) }
        let(:presented_collection) { parser.decode(paginated_collection.send("to_#{format}", :presenter => presenter, :root => collection_element_name, :scope => presenter_scope_for_index)) }
        let(:page) { 1 }
        let(:per_page) { api.default_per_page }

        let(:resource_element_name) { model.name.demodulize.underscore }
        let(:collection_element_name) { resource_element_name.pluralize }
        let(:expected_resource) { model.find(model_resource.id) }
        let(:persisted_resource_id) { body['id'] }
        let(:resource_after_create) { model.find(persisted_resource_id) }
        let(:resource_after_update) { model.find(resource_id) }
        let(:resource_after_destroy) { resource_after_update }
        let(:resource) { parser.decode(expected_resource.send("to_#{format}", :presenter => presenter, :scope => presenter_scope)) }
        let(:presenter_scope) { nil }
        let(:presenter_scope_for_index) { presenter_scope }
        let(:resource_id) { resource['id'] }
        let(:parent_ids) { parent_names.zip(model_parents.map { |m| m.id }) }

        let(:namespace) { nil }
        let(:namespace_path) { ( namespace ? "/#{namespace}" : nil ) }
        let(:parent_path) { parent_ids.map { |p, p_id| "/#{p}/#{p_id}" }.join }
        let(:collection_url) { [ namespace_path, parent_path, "/#{resource_collection_name}.#{format}" ].join }
        let(:resource_url) { [ namespace_path, parent_path, "/#{resource_collection_name}/#{resource_id}.#{format}" ].join }

        let(:request_json_payload) { request_payload.to_json }
        let(:request_xml_payload) { request_payload.to_xml(:root => resource_element_name) }

        let(:malformed_json_payload) { '{ "bad": [ "data": ] }' }
        let(:malformed_xml_payload) { '<bad><data></bad></data>' }

        # DELETE specs expect record to be deleted.
        # Override this if you are using something such as ActiveResource:
        # let(:record_not_found_error) { ActiveResource::ResourceNotFound }
        let(:record_not_found_error) { ActiveRecord::RecordNotFound }

        # Some of the test examples assume that the database is blank.
        # For example, index tests require injecting 3 resources and expects
        # the index endpoint to return exactly 3 resources.
        let(:reset_datastore!) { } # Do nothing by default
      end

      module ClassMethods
        def given_create_attributes(values = {})
          let(:valid_create_attributes) { values }
        end

        def given_update_attributes(values = {})
          let(:valid_update_attributes) { values }
        end

        def metadata_for_resources(resource_name, options = {})
          if resource_name.is_a?(Array)
            parents = resource_name
            resource_name = parents.pop
          end
          resource_name = resource_name.to_s if resource_name.is_a?(Symbol)

          { :formats => [ :json ],
            :resource_name => resource_name,
            :model_name => resource_name.singularize,
            :encoding => 'utf-8',
            :parents => parents || [],
            :namespace_path => ( options[:namespace] ? "/#{options[:namespace]}" : nil )
          }.merge(options)
        end

        def metadata_for_formatted_resource(format, options = {})
          { :format => format,
            :collection_url => collection_url_for_resource(options[:namespace_path], options[:parents], options[:resource_name], format),
            :resource_url => resource_url_for_resource(options[:namespace_path], options[:parents], options[:resource_name], format),
            :mime_type => Mime::Type.lookup_by_extension(format).to_s
          }.merge(options)
        end

        def parent_path_for_resource(parents)
          parents.map { |p| "/#{p}/:id" }.join
        end

        def collection_url_for_resource(namespace, parents, resource_name, format)
          [ namespace, parent_path_for_resource(parents), "/#{resource_name}.#{format}" ].join
        end

        def resource_url_for_resource(namespace, parents, resource_name, format)
          [ namespace, parent_path_for_resource(parents), "/#{resource_name}/:id.#{format}" ].join
        end

        def resources(resource_name, options = {}, &blk)
          options = metadata_for_resources(resource_name, options)
          _resource_name = options[:resource_name].singularize

          # If you want xml, pass it as formats: [ :json, :xml ]
          options[:formats].each do |format|
            format_options = metadata_for_formatted_resource(format, options)
            context format_options[:collection_url], format_options do
              let(:namespace) { options[:namespace] }
              let(:resource_name) { _resource_name }
              let(:parent_names) { options[:parents] }
              let(:format) { format }
              let(options[:parents].last) { model_parent } if options[:parents].last
              instance_eval(&blk) if blk
            end
          end
        end

        def expects_resource_crud(options = {}, &additional_examples)
          expected_actions = options[:only] || [ :index, :show, :create, :update, :destroy ]
          expected_actions -= options[:except] if options[:except]

          expected_actions.each do |action|
            send("expects_#{action}")
          end

          instance_eval(&additional_examples) if additional_examples
        end

        STANDARD_SUCCESSFUL_STATUS_FOR = {
          :index => 200,
          :show => 200,
          :create => 201,
          :update => 200,
          :destroy => 204 }

        STANDARD_REQUEST_FOR = {
          :index => { :method => :get, :end_point => :collection_url },
          :show => { :method => :get, :end_point => :resource_url },
          :create => { :method => :post, :end_point => :collection_url, :payload => proc do valid_create_attributes end },
          :update => { :method => :put, :end_point => :resource_url, :payload => proc do valid_update_attributes end },
          :destroy => { :method => :delete, :end_point => :resource_url } }

        def request_resource_action(action, options = {}, &blk)
          options = {
            :mime_type => metadata[:mime_type],
            :encoding => metadata[:encoding],
            :status => STANDARD_SUCCESSFUL_STATUS_FOR[action],
            :collection_url => metadata[:collection_url],
{'fraction_non_alphanumeric': 0.12447340705634545, 'fraction_numerical': 0.0026329647182727752, 'mean_word_length': 2.8463291139240505, 'pattern_counts': {'":': 4, '<': 11, '<?xml version=': 0, '>': 101, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 10}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3736790', 'n_tokens_mistral': 4343, 'n_tokens_neox': 4266, 'n_words': 815}